Browse Source

*** empty log message ***

David Rose 25 years ago
parent
commit
97cc7e6be8

+ 21 - 7
ppremake/Depends.pp

@@ -6,19 +6,33 @@
 // determining build order.
 // determining build order.
 //
 //
 
 
-#if $[eq $[DIR_TYPE], src]
-
+#if $[or $[eq $[DIR_TYPE], src], $[eq $[DIR_TYPE], metalib]]
 #if $[eq $[DEPENDS],]
 #if $[eq $[DEPENDS],]
-  #map local_libs TARGET(*/lib_target */noinst_lib_target)
-
   // Allow the user to define additional DEPENDS targets in each
   // Allow the user to define additional DEPENDS targets in each
   // Sources.pp.
   // Sources.pp.
   #define DEPENDS
   #define DEPENDS
   #set DEPENDS $[EXTRA_DEPENDS]
   #set DEPENDS $[EXTRA_DEPENDS]
 
 
-  #forscopes lib_target bin_target noinst_bin_target
-    #set DEPENDS $[DEPENDS] $[local_libs $[DIRNAME],$[LOCAL_LIBS]] $[LOCAL_INCS]
-  #end lib_target bin_target noinst_bin_target
+  #forscopes metalib_target static_lib_target lib_target noinst_lib_target bin_target noinst_bin_target
+    // We can optimize quite a bit by evaluating now several of the key
+    // deferred variables defined in Globals.pp.  This way they won't need
+    // to get repeatedly reevaluated as each directory examines each
+    // other.
+    #define build_directory $[build_directory]
+    #define build_target $[build_target]
+    #define active_local_libs $[active_local_libs]
+    #define active_component_libs $[active_component_libs]
+    #define active_libs $[active_libs]
+    #define get_sources $[get_sources]
+
+    // Report a warning for nonexisting dependencies.
+    #define nonexisting $[unmapped all_libs,$[LOCAL_LIBS]]
+    #if $[ne $[nonexisting],]
+Warning: Lib(s) $[nonexisting], referenced in $[DIRNAME]/$[TARGET], not found.
+    #endif
+
+    #set DEPENDS $[DEPENDS] $[all_libs $[DIRNAME],$[LOCAL_LIBS] $[COMPONENT_LIBS]] $[LOCAL_INCS]
+  #end metalib_target static_lib_target lib_target noinst_lib_target bin_target noinst_bin_target
 
 
   #set DEPENDS $[sort $[DEPENDS]]
   #set DEPENDS $[sort $[DEPENDS]]
 #endif
 #endif

+ 23 - 190
ppremake/Global.stopgap.pp

@@ -6,196 +6,36 @@
 // Template.stopgap.pp.
 // Template.stopgap.pp.
 //
 //
 
 
-// Define some various compile flags, derived from the variables set
-// in Config.pp.
-#if $[HAVE_PYTHON]
-  // We want to let the PYTHON_INCLUDE directory include wildcard characters.
-  #define python_ipath $[patsubst %,-I%,$[isdir $[PYTHON_IPATH]]]
-  #define python_lpath $[patsubst %,-L%,$[isdir $[PYTHON_LPATH]]]
-#endif
-
-#if $[HAVE_NSPR]
-  // We want to let the NSPR directories include wildcard characters.
-  #define nspr_ipath $[patsubst %,-I%,$[isdir $[NSPR_IPATH]]]
-  #define nspr_lpath $[patsubst %,-L%,$[isdir $[NSPR_LPATH]]]
-  #define nspr_libs $[NSPR_LIBS]
-#endif
-
-#if $[HAVE_ZLIB]
-  #define zlib_ipath $[ZLIB_IPATH:%=-I%]
-  #define zlib_lpath $[ZLIB_LPATH:%=-L%]
-  #define zlib_libs $[ZLIB_LIBS]
-#endif
-
-#if $[HAVE_SOXST]
-  #define soxst_ipath $[SOXST_IPATH:%=-I%]
-  #define soxst_lpath $[SOXST_LPATH:%=-L%]
-  #define soxst_libs $[SOXST_LIBS]
-#endif
-
-#if $[HAVE_GL]
-  #define gl_ipath $[GL_IPATH:%=-I%]
-  #define gl_lpath $[GL_LPATH:%=-L%]
-  #define gl_libs $[GL_LIBS]
-#endif
-
-#if $[HAVE_DX]
-  #define dx_ipath $[DX_IPATH:%=-I%]
-  #define dx_lpath $[DX_LPATH:%=-L%]
-  #define dx_libs $[DX_LIBS]
-#endif
-
-#if $[HAVE_MIKMOD]
-  #define mikmod_ipath $[MIKMOD_IPATH:%=-I%]
-  #define mikmod_cflags $[MIKMOD_CFLAGS]
-  #define mikmod_lpath $[MIKMOD_LPATH:%=-L%]
-  #define mikmod_libs $[MIKMOD_LIBS]
-#endif
-
-#if $[HAVE_GTKMM]
-  #define gtkmm_ipath $[GTKMM_IPATH:%=-I%]
-  #define gtkmm_cflags $[GTKMM_CFLAGS]
-  #define gtkmm_lpath $[GTKMM_LPATH:%=-L%]
-  #define gtkmm_libs $[GTKMM_LIBS]
-#endif
-
-#if $[and $[HAVE_MAYA],$[MAYA_LOCATION]]
-  #define maya_ipath -I$[MAYA_LOCATION]/include
-  #define maya_lpath -L$[MAYA_LOCATION]/lib
-  #define maya_ld $[MAYA_LOCATION]/bin/mayald
-#endif
-
-#if $[HAVE_NET]
-  #define net_ipath $[NET_IPATH:%=-I%]
-  #define net_lpath $[NET_LPATH:%=-L%]
-  #define net_libs $[NET_LIBS]
-#endif
-
-#if $[HAVE_AUDIO]
-  #define audio_ipath $[AUDIO_IPATH:%=-I%]
-  #define audio_lpath $[AUDIO_LPATH:%=-L%]
-  #define audio_libs $[AUDIO_LIBS]
-#endif
-
-
-// This variable, when evaluated in the scope of a particular directory,
-// will indicate true (i.e. nonempty) when the directory is truly built,
-// or false (empty) when the directory is not to be built.
-#defer build_directory \
- $[and \
-     $[or $[not $[DIRECTORY_IF_GL]],$[HAVE_GL]], \
-     $[or $[not $[DIRECTORY_IF_DX]],$[HAVE_DX]], \
-     $[or $[not $[DIRECTORY_IF_GLX]],$[HAVE_GLX]], \
-     $[or $[not $[DIRECTORY_IF_GLUT]],$[HAVE_GLUT]], \
-     $[or $[not $[DIRECTORY_IF_WGL]],$[HAVE_WGL]], \
-     $[or $[not $[DIRECTORY_IF_RIB]],$[HAVE_RIB]], \
-     $[or $[not $[DIRECTORY_IF_PS2]],$[HAVE_PS2]], \
-     $[or $[not $[DIRECTORY_IF_SGIGL]],$[HAVE_SGIGL]], \
-     $[or $[not $[DIRECTORY_IF_VRPN]],$[HAVE_VRPN]], \
-     $[or $[not $[DIRECTORY_IF_NET]],$[HAVE_NET]], \
-     $[or $[not $[DIRECTORY_IF_AUDIO]],$[HAVE_AUDIO]], \
-     $[or $[not $[DIRECTORY_IF_GTKMM]],$[HAVE_GTKMM]], \
-     $[or $[not $[DIRECTORY_IF_MAYA]],$[HAVE_MAYA]], \
-      1 ]
-
-// This variable is true if we are building on some flavor of Unix.
-#define unix_platform $[ne $[PLATFORM],Win32]
-
-// This variable is true if we are building on some flavor of Windows.
-#define windows_platform $[eq $[PLATFORM],Win32]
-
-
-// This subroutine will set up the sources variable to reflect the
-// complete set of sources for this target, and also set the
-// alt_cflags, alt_libs, etc. as appropriate according to how the
-// various USE_* flags are set for the current target.
+// This subroutine fills sources, alt_cflags, alt_ipath, alt_lpath,
+// alt_libs, and alt_ld as appropriate for the current target.
+#define sources
+#define alt_cflags
+#define alt_ipath
+#define alt_lpath
+#define alt_libs
+#define alt_ld
 #defsub get_sources
 #defsub get_sources
-  #define sources $[SOURCES]
-  #if $[ne $[HAVE_ZLIB],]
-    #set sources $[sources] $[IF_ZLIB_SOURCES]
-  #endif
-  #if $[ne $[HAVE_PYTHON],]
-    #set sources $[sources] $[IF_PYTHON_SOURCES]
-  #endif
-  
-  #define alt_cflags $[nspr_cflags] $[mikmod_cflags] $[python_cflags]
-  #define alt_ipath $[nspr_ipath] $[mikmod_ipath] $[python_ipath]
-  #define alt_lpath $[nspr_lpath] $[mikmod_lpath] $[python_lpath]
-  #define alt_libs $[nspr_libs] $[mikmod_libs]
-  #define alt_ld
-
-  // If any of a metalib's constituent libraries require interrogate,
-  // then so does the metalib itself.  To look this up, we need this map
-  // variable.
-  #map components TARGET(*/lib_target */noinst_lib_target)
-  
-  #if $[ne $[USE_ZLIB] $[components $[USE_ZLIB],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[zlib_cflags]
-    #set alt_ipath $[alt_ipath] $[zlib_ipath]
-    #set alt_lpath $[alt_lpath] $[zlib_lpath]
-    #set alt_libs $[alt_libs] $[zlib_libs]
-  #endif
-  #if $[ne $[USE_GL] $[components $[USE_GL],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[gl_cflags]
-    #set alt_ipath $[alt_ipath] $[gl_ipath]
-    #set alt_lpath $[alt_lpath] $[gl_lpath]
-    #set alt_libs $[alt_libs] $[gl_libs]
-  #endif
-  #if $[ne $[USE_DX] $[components $[USE_DX],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[dx_cflags]
-    #set alt_ipath $[alt_ipath] $[dx_ipath]
-    #set alt_lpath $[alt_lpath] $[dx_lpath]
-    #set alt_libs $[alt_libs] $[dx_libs]
-  #endif
-  #if $[ne $[USE_SOXST] $[components $[USE_SOXST],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[soxst_cflags]
-    #set alt_ipath $[alt_ipath] $[soxst_ipath]
-    #set alt_lpath $[alt_lpath] $[soxst_lpath]
-    #set alt_libs $[alt_libs] $[soxst_libs]
-  #endif
-  #if $[ne $[USE_NET] $[components $[USE_NET],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[net_cflags]
-    #set alt_ipath $[alt_ipath] $[net_ipath]
-    #set alt_lpath $[alt_lpath] $[net_lpath]
-    #set alt_libs $[alt_libs] $[net_libs]
-  #endif
-  #if $[ne $[USE_AUDIO] $[components $[USE_AUDIO],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[audio_cflags]
-    #set alt_ipath $[alt_ipath] $[audio_ipath]
-    #set alt_lpath $[alt_lpath] $[audio_lpath]
-    #set alt_libs $[alt_libs] $[audio_libs]
-  #endif
-  #if $[ne $[USE_GTKMM] $[components $[USE_GTKMM],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[gtkmm_cflags]
-    #set alt_ipath $[alt_ipath] $[gtkmm_ipath]
-    #set alt_lpath $[alt_lpath] $[gtkmm_lpath]
-    #set alt_libs $[alt_libs] $[gtkmm_libs]
-  #endif 
-  #if $[ne $[USE_MAYA] $[components $[USE_MAYA],$[COMPONENT_LIBS]],]
-    #set alt_cflags $[alt_cflags] $[maya_cflags]
-    #set alt_ipath $[alt_ipath] $[maya_ipath]
-    #set alt_lpath $[alt_lpath] $[maya_lpath]
-    #set alt_libs $[alt_libs] $[maya_libs]
-    #set alt_ld $[maya_ld]
-  #endif
-  #if $[unix_platform]
-    #set alt_libs $[alt_libs] $[UNIX_SYS_LIBS] $[components $[UNIX_SYS_LIBS],$[COMPONENT_LIBS]]
-  #endif
+  #set sources $[get_sources]
+  #set alt_cflags $[get_cflags]
+  #set alt_ipath $[get_ipath]
+  #set alt_lpath $[get_lpath]
+  #set alt_libs $[get_libs]
+  #set alt_ld $[get_ld]
 #end get_sources
 #end get_sources
 
 
 // This subroutine will set when_defer, when_no_defer, and when_either
 // This subroutine will set when_defer, when_no_defer, and when_either
-// correctly to the set of libs we should link with for current
+// correctly to the set of libs we should link with for the current
 // target.
 // target.
+#define when_defer
+#define when_no_defer
+#define when_either
 #defsub get_libs
 #defsub get_libs
   // For the WHEN_DEFER case, we need to know the complete set of
   // For the WHEN_DEFER case, we need to know the complete set of
   // metalibs that encapsulates each of our LOCAL_LIBS.  In the case
   // metalibs that encapsulates each of our LOCAL_LIBS.  In the case
   // where a particular library is not part of a metalib, we include the
   // where a particular library is not part of a metalib, we include the
   // library itself.
   // library itself.
   
   
-  // These map variables are handy to determine that.
-  #map module COMPONENT_LIBS(*/metalib_target)
-  #map all_libs TARGET(*/static_lib_target */lib_target */noinst_lib_target */metalib_target)
-  #define when_defer
+  #set when_defer
   #foreach lib $[LOCAL_LIBS]
   #foreach lib $[LOCAL_LIBS]
     // Only consider libraries that we're actually building.
     // Only consider libraries that we're actually building.
     #if $[all_libs $[build_directory],$[lib]]
     #if $[all_libs $[build_directory],$[lib]]
@@ -211,7 +51,7 @@
   
   
   // Also filter out the libraries we don't want from when_no_defer, although
   // Also filter out the libraries we don't want from when_no_defer, although
   // we don't need to translate these to metalibs.
   // we don't need to translate these to metalibs.
-  #define when_no_defer
+  #set when_no_defer
   #foreach lib $[COMPONENT_LIBS] $[LOCAL_LIBS]
   #foreach lib $[COMPONENT_LIBS] $[LOCAL_LIBS]
     #if $[all_libs $[build_directory],$[lib]]
     #if $[all_libs $[build_directory],$[lib]]
       #set when_no_defer $[when_no_defer] $[lib]
       #set when_no_defer $[when_no_defer] $[lib]
@@ -222,7 +62,7 @@
   // Finally, get the set of libraries that we want in either case.  At
   // Finally, get the set of libraries that we want in either case.  At
   // the moment, this is just the set of libraries in OTHER_LIBS that's
   // the moment, this is just the set of libraries in OTHER_LIBS that's
   // not flagged with either a :c or a :m.
   // not flagged with either a :c or a :m.
-  #define when_either $[filter-out %:m %:c,$[OTHER_LIBS]]
+  #set when_either $[filter-out %:m %:c,$[OTHER_LIBS]]
 #end get_libs
 #end get_libs
 
 
 
 
@@ -230,9 +70,6 @@
 // to a list of the form libname.so or libname.a, according to whether the
 // to a list of the form libname.so or libname.a, according to whether the
 // named libraries are static or dynamic.
 // named libraries are static or dynamic.
 #defsub convert_depend_libs
 #defsub convert_depend_libs
-  #map static_libs TARGET(*/static_lib_target)
-  #map dynamic_libs TARGET(*/lib_target */metalib_target)
-  #map all_libs TARGET(*/static_lib_target */lib_target */metalib_target)
   #define new_depend_libs
   #define new_depend_libs
   #foreach lib $[depend_libs]
   #foreach lib $[depend_libs]
     // Make sure the library is something we're actually building.
     // Make sure the library is something we're actually building.
@@ -258,10 +95,9 @@
 // and (b) indirectly on all of the metalibs that every other library
 // and (b) indirectly on all of the metalibs that every other library
 // dependency is part of.  If a target is not part of a metalib, it is
 // dependency is part of.  If a target is not part of a metalib, it is
 // the same as case (b) above.
 // the same as case (b) above.
+#define depend_libs
 #defsub get_depend_libs
 #defsub get_depend_libs
-  #map module COMPONENT_LIBS(*/metalib_target)
-
-  #define depend_libs
+  #set depend_libs
   #forscopes lib_target noinst_lib_target
   #forscopes lib_target noinst_lib_target
     #define metalib $[module $[TARGET],$[TARGET]]
     #define metalib $[module $[TARGET],$[TARGET]]
     #if $[ne $[metalib],]
     #if $[ne $[metalib],]
@@ -314,9 +150,6 @@
 
 
   // Now correct all the libraries listed in depend_libs to refer to a
   // Now correct all the libraries listed in depend_libs to refer to a
   // real library name.
   // real library name.
-  #map static_libs TARGET(*/static_lib_target)
-  #map dynamic_libs TARGET(*/lib_target */metalib_target)
-  #map all_libs TARGET(*/static_lib_target */lib_target */metalib_target)
   #define new_depend_libs
   #define new_depend_libs
   #foreach lib $[sort $[depend_libs]]
   #foreach lib $[sort $[depend_libs]]
     // Make sure the library is something we're actually building.
     // Make sure the library is something we're actually building.

+ 6 - 4
ppremake/Makefile.am

@@ -12,9 +12,11 @@ ppremake_SOURCES =						\
     tokenize.h
     tokenize.h
 
 
 data_DATA = \
 data_DATA = \
-    System.pp Depends.pp Template.autoconf.pp \
-    Global.stopgap.pp Template.stopgap.pp
+    System.pp Depends.pp Global.pp Template.autoconf.pp \
+    Global.stopgap.pp Template.stopgap.pp \
+    Global.unix.pp Template.unix.pp
 
 
 EXTRA_DIST =\
 EXTRA_DIST =\
-    System.pp Depends.pp Template.autoconf.pp \
-    Global.stopgap.pp Template.stopgap.pp
+    System.pp Depends.pp Global.pp Template.autoconf.pp \
+    Global.stopgap.pp Template.stopgap.pp \
+    Global.unix.pp Template.unix.pp

+ 5 - 6
ppremake/System.pp

@@ -21,14 +21,13 @@
 #endif
 #endif
 
 
 #if $[eq $[GLOBAL_FILE],]
 #if $[eq $[GLOBAL_FILE],]
-  #define GLOBAL_FILE $[PPREMAKE_DIR]/Global.$[BUILD_TYPE].pp
+  #define GLOBAL_FILE $[PPREMAKE_DIR]/Global.pp
+#endif
+
+#if $[eq $[GLOBAL_TYPE_FILE],]
+  #define GLOBAL_TYPE_FILE $[PPREMAKE_DIR]/Global.$[BUILD_TYPE].pp
 #endif
 #endif
 
 
 #if $[eq $[TEMPLATE_FILE],]
 #if $[eq $[TEMPLATE_FILE],]
   #define TEMPLATE_FILE $[PPREMAKE_DIR]/Template.$[BUILD_TYPE].pp
   #define TEMPLATE_FILE $[PPREMAKE_DIR]/Template.$[BUILD_TYPE].pp
 #endif
 #endif
-
-// Include the global definitions for the template type, if the file
-// is there.
-#sinclude $[GLOBAL_FILE]
-

+ 34 - 44
ppremake/Template.stopgap.pp

@@ -16,11 +16,6 @@
 #define submakes $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target test_bin_target)]
 #define submakes $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target test_bin_target)]
 #define install $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target)]
 #define install $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target)]
 
 
-
-// This map variable lets us identify which metalib, if any, is
-// including each library built here.
-#map module COMPONENT_LIBS(*/metalib_target)
-
 // Now iterate through the libraries we're building and see which ones
 // Now iterate through the libraries we're building and see which ones
 // actually *are* being included in a metalib.  For each one that is,
 // actually *are* being included in a metalib.  For each one that is,
 // we install the appropriate deferred file.
 // we install the appropriate deferred file.
@@ -40,7 +35,7 @@
 #define install_bin $[sort $[TARGET(bin_target)] $[INSTALL_BIN]]
 #define install_bin $[sort $[TARGET(bin_target)] $[INSTALL_BIN]]
 #define install_scripts $[sort $[INSTALL_SCRIPTS(static_lib_target lib_target bin_target)] $[TARGET(sed_bin_target)] $[INSTALL_SCRIPTS]]
 #define install_scripts $[sort $[INSTALL_SCRIPTS(static_lib_target lib_target bin_target)] $[TARGET(sed_bin_target)] $[INSTALL_SCRIPTS]]
 #define install_headers $[sort $[INSTALL_HEADERS(static_lib_target lib_target bin_target)] $[INSTALL_HEADERS]]
 #define install_headers $[sort $[INSTALL_HEADERS(static_lib_target lib_target bin_target)] $[INSTALL_HEADERS]]
-#define install_data $[sort $[INSTALL_DATA(static_lib_target lib_target sed_bin_target bin_target)] $[INSTALL_DATA]]
+#define install_data $[sort $[INSTALL_DATA(static_lib_target lib_target sed_bin_target bin_target)] $[INSTALL_DATA]] $[sort $[INSTALL_CONFIG(static_lib_target lib_target sed_bin_target bin_target)] $[INSTALL_CONFIG]]
 
 
 // Collect the set of interrogate database files we'll install,
 // Collect the set of interrogate database files we'll install,
 // possibly one for each library we build.
 // possibly one for each library we build.
@@ -71,7 +66,7 @@ INSTALL = $[install]
 MAKEDIR = .
 MAKEDIR = .
 
 
 #### The action is here.
 #### The action is here.
-include $(DTOOL)/inc/Makefile.meta.rules
+include $(DTOOL)/include/Makefile.meta.rules
 
 
 #### Sub-make build order dependencies:
 #### Sub-make build order dependencies:
 # foo: bar
 # foo: bar
@@ -123,14 +118,14 @@ IGATEDB =$[install_igatedb]
 #if $[ne $[INSTALL_PARSER_INC],]
 #if $[ne $[INSTALL_PARSER_INC],]
 PARSER_INC = $[INSTALL_PARSER_INC]
 PARSER_INC = $[INSTALL_PARSER_INC]
 SRC_PARSER_INC = $(addprefix $(PKGROOT)/,$(PARSER_INC))
 SRC_PARSER_INC = $(addprefix $(PKGROOT)/,$(PARSER_INC))
-INST_PARSER_INC = $(addprefix inc/parser-inc/,$(PARSER_INC))
+INST_PARSER_INC = $(addprefix include/parser-inc/,$(PARSER_INC))
 OTHER = $(INST_PARSER_INC)
 OTHER = $(INST_PARSER_INC)
 #else
 #else
 # OTHER =
 # OTHER =
 #endif
 #endif
 
 
 #### Where the action happens.
 #### Where the action happens.
-include $(DTOOL)/inc/Makefile.install.rules
+include $(DTOOL)/include/Makefile.install.rules
 
 
 #### Install actions for OTHER files (source must be in $(PKGROOT)):
 #### Install actions for OTHER files (source must be in $(PKGROOT)):
 # [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
 # [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
@@ -140,7 +135,7 @@ include $(DTOOL)/inc/Makefile.install.rules
 #	$(MKINSTALL)			# Also makes directory if needed
 #	$(MKINSTALL)			# Also makes directory if needed
 
 
 #if $[ne $[INSTALL_PARSER_INC],]
 #if $[ne $[INSTALL_PARSER_INC],]
-$(INST_PARSER_INC) : inc/parser-inc/% : $(PKGROOT)/%
+$(INST_PARSER_INC) : include/parser-inc/% : $(PKGROOT)/%
 	$(MKINSTALL)
 	$(MKINSTALL)
 #endif
 #endif
 
 
@@ -226,11 +221,11 @@ C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
 
 
 #### Interrogate info
 #### Interrogate info
 IGATESCAN  = $[igatescan]
 IGATESCAN  = $[igatescan]
-IGATEFLAGS = $[alt_ipath]
+IGATEFLAGS = $[alt_ipath:%=-I%]
 # IGATEFILE  = # Specify only if you want a specific name
 # IGATEFILE  = # Specify only if you want a specific name
 
 
 #### Additional search directories for C/C++ header files:
 #### Additional search directories for C/C++ header files:
-IPATH = $[alt_ipath]
+IPATH = $[alt_ipath:%=-I%]
 
 
 #### Location to put .o files:
 #### Location to put .o files:
 # ODIR = 
 # ODIR = 
@@ -246,7 +241,7 @@ LIBS = $[when_either:%=-l%]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 
 
 #### Additional search directories for lib:
 #### Additional search directories for lib:
-LPATH = $[alt_lpath]
+LPATH = $[alt_lpath:%=-L%]
 
 
 #### Other linker flags. 
 #### Other linker flags. 
 #if $[ne $[alt_ld],]
 #if $[ne $[alt_ld],]
@@ -255,16 +250,16 @@ LD = $[alt_ld]
 # LDFLAGS = 
 # LDFLAGS = 
 
 
 #### Pull in standard .o make variables
 #### Pull in standard .o make variables
-include $(DTOOL)/inc/Makefile.o.vars
+include $(DTOOL)/include/Makefile.o.vars
 
 
 #### The .o action is here.
 #### The .o action is here.
-include $(DTOOL)/inc/Makefile.o.rules
+include $(DTOOL)/include/Makefile.o.rules
 
 
 #### Pull in standard binary make variables.
 #### Pull in standard binary make variables.
-include $(DTOOL)/inc/Makefile.bin.vars
+include $(DTOOL)/include/Makefile.bin.vars
 
 
 #### The .so action is here.
 #### The .so action is here.
-include $(DTOOL)/inc/Makefile.so.rules
+include $(DTOOL)/include/Makefile.so.rules
 #end Makefile.$[TARGET].so
 #end Makefile.$[TARGET].so
 
 
 #end lib_target noinst_lib_target
 #end lib_target noinst_lib_target
@@ -314,7 +309,7 @@ C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
 # PTREPOSITORY = # Specify only if you want a specific name
 # PTREPOSITORY = # Specify only if you want a specific name
 
 
 #### Additional search directories for C/C++ header files:
 #### Additional search directories for C/C++ header files:
-IPATH = $[alt_ipath]
+IPATH = $[alt_ipath:%=-I%]
 
 
 #### Location to put .o files:
 #### Location to put .o files:
 # ODIR = 
 # ODIR = 
@@ -332,22 +327,22 @@ LIBS = $[when_either:%=-l%]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 
 
 #### Additional search directories for lib:
 #### Additional search directories for lib:
-LPATH = $[alt_lpath]
+LPATH = $[alt_lpath:%=-L%]
 
 
 #### Archiver flags
 #### Archiver flags
 # ARFLAGS = 
 # ARFLAGS = 
 
 
 #### Pull in standard .o make variables
 #### Pull in standard .o make variables
-include $(DTOOL)/inc/Makefile.o.vars
+include $(DTOOL)/include/Makefile.o.vars
 
 
 #### The .o action is here.
 #### The .o action is here.
-include $(DTOOL)/inc/Makefile.o.rules
+include $(DTOOL)/include/Makefile.o.rules
 
 
 #### Pull in standard binary make variables.
 #### Pull in standard binary make variables.
-include $(DTOOL)/inc/Makefile.bin.vars
+include $(DTOOL)/include/Makefile.bin.vars
 
 
 #### The .a action is here.
 #### The .a action is here.
-include $(DTOOL)/inc/Makefile.a.rules
+include $(DTOOL)/include/Makefile.a.rules
 #end Makefile.$[TARGET].a
 #end Makefile.$[TARGET].a
 
 
 #end static_lib_target
 #end static_lib_target
@@ -395,7 +390,7 @@ C++FILES = $[filter %.cxx,$[sources]]
 C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
 C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
 
 
 #### Additional search directories for C/C++ header files:
 #### Additional search directories for C/C++ header files:
-IPATH = $[alt_ipath]
+IPATH = $[alt_ipath:%=-I%]
 
 
 #### Location to put .o files:
 #### Location to put .o files:
 # ODIR = 
 # ODIR = 
@@ -411,7 +406,7 @@ LIBS = $[when_either:%=-l%]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 
 
 #### Additional search directories for lib:
 #### Additional search directories for lib:
-LPATH = $[alt_lpath]
+LPATH = $[alt_lpath:%=-L%]
 
 
 #### Other linker flags. 
 #### Other linker flags. 
 #if $[ne $[alt_ld],]
 #if $[ne $[alt_ld],]
@@ -420,16 +415,16 @@ LD = $[alt_ld]
 # LDFLAGS =
 # LDFLAGS =
 
 
 #### Pull in standard .o make variables
 #### Pull in standard .o make variables
-include $(DTOOL)/inc/Makefile.o.vars
+include $(DTOOL)/include/Makefile.o.vars
 
 
 #### The .o action is here.
 #### The .o action is here.
-include $(DTOOL)/inc/Makefile.o.rules
+include $(DTOOL)/include/Makefile.o.rules
 
 
 #### Pull in standard binary make variables.
 #### Pull in standard binary make variables.
-include $(DTOOL)/inc/Makefile.bin.vars
+include $(DTOOL)/include/Makefile.bin.vars
 
 
 #### The bin action is here.
 #### The bin action is here.
-include $(DTOOL)/inc/Makefile.bin.rules
+include $(DTOOL)/include/Makefile.bin.rules
 #end Makefile.$[TARGET]
 #end Makefile.$[TARGET]
 
 
 #end bin_target noinst_bin_target test_bin_target
 #end bin_target noinst_bin_target test_bin_target
@@ -465,17 +460,13 @@ cleanall :
 #define submakes $[TARGET(metalib_target):%=%.so]
 #define submakes $[TARGET(metalib_target):%=%.so]
 
 
 
 
-// This map variable lets us identify which metalib, if any, is
-// including each library built here.
-#map module COMPONENT_LIBS(*/metalib_target)
-
 // Get the full set of libraries we depend on.
 // Get the full set of libraries we depend on.
 #call get_depend_libs
 #call get_depend_libs
 
 
 // Also get the targets we'll be installing.
 // Also get the targets we'll be installing.
 #define install_libs $[TARGET(metalib_target):%=lib%.so]
 #define install_libs $[TARGET(metalib_target):%=lib%.so]
 #define install_headers $[INSTALL_HEADERS(metalib_target)]
 #define install_headers $[INSTALL_HEADERS(metalib_target)]
-#define install_data $[INSTALL_DATA(metalib_target)]
+#define install_data $[INSTALL_DATA(metalib_target)] $[INSTALL_CONFIG(metalib_target)]
 
 
 
 
 #output Makefile
 #output Makefile
@@ -495,7 +486,7 @@ INSTALL = $[submakes]
 MAKEDIR = .
 MAKEDIR = .
 
 
 #### The action is here.
 #### The action is here.
-include $(DTOOL)/inc/Makefile.meta.rules
+include $(DTOOL)/include/Makefile.meta.rules
 
 
 #### Sub-make build order dependencies:
 #### Sub-make build order dependencies:
 # foo: bar
 # foo: bar
@@ -544,7 +535,7 @@ INCLUDE = $[install_headers]
 # OTHER = 
 # OTHER = 
 
 
 #### Where the action happens.
 #### Where the action happens.
-include $(DTOOL)/inc/Makefile.install.rules
+include $(DTOOL)/include/Makefile.install.rules
 
 
 #### Install actions for OTHER files (source must be in $(PKGROOT)):
 #### Install actions for OTHER files (source must be in $(PKGROOT)):
 # [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
 # [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
@@ -576,7 +567,6 @@ endif
 #call get_libs
 #call get_libs
 
 
 #if $[HAVE_PYTHON]
 #if $[HAVE_PYTHON]
-  #map components TARGET(*/lib_target */noinst_lib_target)
   #if $[ne $[components $[IGATESCAN],$[COMPONENT_LIBS]],]
   #if $[ne $[components $[IGATESCAN],$[COMPONENT_LIBS]],]
     #define igatemscan $[TARGET]
     #define igatemscan $[TARGET]
   #endif
   #endif
@@ -627,7 +617,7 @@ IGATEMSCAN = $[igatemscan]
 DEFERRED_FILES = $[TARGET]
 DEFERRED_FILES = $[TARGET]
 
 
 #### Additional search directories for C/C++ header files:
 #### Additional search directories for C/C++ header files:
-IPATH = $[alt_ipath]
+IPATH = $[alt_ipath:%=-I%]
 
 
 #### Location to put .o files:
 #### Location to put .o files:
 # ODIR = 
 # ODIR = 
@@ -643,7 +633,7 @@ LIBS = $[when_either:%=-l%]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
 
 
 #### Additional search directories for lib:
 #### Additional search directories for lib:
-LPATH = $[alt_lpath]
+LPATH = $[alt_lpath:%=-L%]
 
 
 #### Other linker flags. 
 #### Other linker flags. 
 #if $[ne $[alt_ld],]
 #if $[ne $[alt_ld],]
@@ -652,16 +642,16 @@ LD = $[alt_ld]
 # LDFLAGS = 
 # LDFLAGS = 
 
 
 #### Pull in standard .o make variables
 #### Pull in standard .o make variables
-include $(DTOOL)/inc/Makefile.o.vars
+include $(DTOOL)/include/Makefile.o.vars
 
 
 #### The .o action is here.
 #### The .o action is here.
-include $(DTOOL)/inc/Makefile.o.rules
+include $(DTOOL)/include/Makefile.o.rules
 
 
 #### Pull in standard binary make variables.
 #### Pull in standard binary make variables.
-include $(DTOOL)/inc/Makefile.bin.vars
+include $(DTOOL)/include/Makefile.bin.vars
 
 
 #### The .so action is here.
 #### The .so action is here.
-include $(DTOOL)/inc/Makefile.so.rules
+include $(DTOOL)/include/Makefile.so.rules
 #end Makefile.$[TARGET].so
 #end Makefile.$[TARGET].so
 
 
 #end metalib_target
 #end metalib_target
@@ -694,7 +684,7 @@ include $(DTOOL)/inc/Makefile.so.rules
 CTPROJECT = $[PACKAGE]
 CTPROJECT = $[PACKAGE]
 CTPROJROOT = $($[upcase $[PACKAGE]])
 CTPROJROOT = $($[upcase $[PACKAGE]])
 
 
-include $(DTOOL)/inc/Makefile.project.vars
+include $(DTOOL)/include/Makefile.project.vars
 
 
 // Iterate through all of our known source files.  Each src and
 // Iterate through all of our known source files.  Each src and
 // metalib type file gets its corresponding Makefile.install listed
 // metalib type file gets its corresponding Makefile.install listed

+ 1 - 1
ppremake/configure.in

@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT(ppremake.cxx)
 AC_INIT(ppremake.cxx)
-AM_INIT_AUTOMAKE(ppremake, 0.25a)
+AM_INIT_AUTOMAKE(ppremake, 0.26)
 AM_CONFIG_HEADER(config.h)
 AM_CONFIG_HEADER(config.h)
 
 
 AC_PREFIX_DEFAULT(/usr/local/panda)
 AC_PREFIX_DEFAULT(/usr/local/panda)

+ 301 - 36
ppremake/ppCommandFile.cxx

@@ -152,6 +152,18 @@ PPCommandFile::
   delete _write_state;
   delete _write_state;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::set_output
+//       Access: Public
+//  Description: Changes the main output stream that will be written
+//               to when text appears outside of a #output .. #end
+//               block.  This is cout by default.
+////////////////////////////////////////////////////////////////////
+void PPCommandFile::
+set_output(ostream *out) {
+  _write_state->_out = out;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::set_scope
 //     Function: PPCommandFile::set_scope
 //       Access: Public
 //       Access: Public
@@ -343,10 +355,19 @@ end_read() {
       cerr << "Unclosed foreach " << _block_nesting->_name << "\n";
       cerr << "Unclosed foreach " << _block_nesting->_name << "\n";
       break;
       break;
 
 
+    case BS_formap:
+    case BS_nested_formap:
+      cerr << "Unclosed formap " << _block_nesting->_name << "\n";
+      break;
+
     case BS_defsub:
     case BS_defsub:
       cerr << "Unclosed defsub " << _block_nesting->_name << "\n";
       cerr << "Unclosed defsub " << _block_nesting->_name << "\n";
       break;
       break;
 
 
+    case BS_defun:
+      cerr << "Unclosed defun " << _block_nesting->_name << "\n";
+      break;
+
     case BS_output:
     case BS_output:
       cerr << "Unclosed output " << _block_nesting->_name << "\n";
       cerr << "Unclosed output " << _block_nesting->_name << "\n";
       break;
       break;
@@ -424,14 +445,23 @@ handle_command(const string &line) {
   } else if (_command == "foreach") {
   } else if (_command == "foreach") {
     return handle_foreach_command();
     return handle_foreach_command();
 
 
+  } else if (_command == "formap") {
+    return handle_formap_command();
+
   } else if (_command == "format") {
   } else if (_command == "format") {
     return handle_format_command();
     return handle_format_command();
 
 
   } else if (_command == "output") {
   } else if (_command == "output") {
     return handle_output_command();
     return handle_output_command();
 
 
+  } else if (_command == "print") {
+    return handle_print_command();
+
   } else if (_command == "defsub") {
   } else if (_command == "defsub") {
-    return handle_defsub_command();
+    return handle_defsub_command(true);
+
+  } else if (_command == "defun") {
+    return handle_defsub_command(false);
 
 
   } else if (_command == "end") {
   } else if (_command == "end") {
     return handle_end_command();
     return handle_end_command();
@@ -464,6 +494,9 @@ handle_command(const string &line) {
 
 
   } else if (_command == "map") {
   } else if (_command == "map") {
     return handle_map_command();
     return handle_map_command();
+
+  } else if (_command == "addmap") {
+    return handle_addmap_command();
   }
   }
    
    
   cerr << "Invalid command: " << COMMAND_PREFIX << _command << "\n";
   cerr << "Invalid command: " << COMMAND_PREFIX << _command << "\n";
@@ -700,6 +733,7 @@ handle_foreach_command() {
   nest->_scope = _scope;
   nest->_scope = _scope;
   nest->_next = _block_nesting;
   nest->_next = _block_nesting;
 
 
+  // We insert in all but the first word in the words vector.
   nest->_words.insert(nest->_words.end(), words.begin() + 1, words.end());
   nest->_words.insert(nest->_words.end(), words.begin() + 1, words.end());
 
 
   _block_nesting = nest;
   _block_nesting = nest;
@@ -712,6 +746,49 @@ handle_foreach_command() {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_formap_command
+//       Access: Protected
+//  Description: Handles the #formap command: interpret all the lines
+//               between this command and the corresponding #end
+//               command once for each key in the map, and also within
+//               the corresponding scope of that particular key.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_formap_command() {
+  // Get the parameters of the formap command.  The first word is the
+  // name of the variable to substitute in (and which should appear on
+  // the matching #end command), and the second word is the name of
+  // the map variable.
+  vector<string> words;
+  tokenize_whitespace(_scope->expand_string(_params), words);
+
+  if (words.size() != 2) {
+    cerr << "#formap requires exactly two parameters.\n";
+    return false;
+  }
+
+  string variable_name = words.front();
+
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = _in_for ? BS_nested_formap : BS_formap;
+  nest->_name = words[0];
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  nest->_words.push_back(words[1]);
+
+  _block_nesting = nest;
+
+  if (!_in_for) {
+    _in_for = true;
+    _saved_lines.clear();
+  }
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::handle_format_command
 //     Function: PPCommandFile::handle_format_command
 //       Access: Protected
 //       Access: Protected
@@ -801,36 +878,75 @@ handle_output_command() {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_print_command
+//       Access: Protected
+//  Description: Handles the #print command: immediately output the
+//               arguments to this line to standard error.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_print_command() {
+  if (!_in_for) {
+    cerr << _scope->expand_string(_params) << "\n";
+  }
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::handle_defsub_command
 //     Function: PPCommandFile::handle_defsub_command
 //       Access: Protected
 //       Access: Protected
-//  Description: Handles the #defsub command: save all the lines
-//               between this command and the matching #end as a
+//  Description: Handles the #defsub (or #defun) command: save all the
+//               lines between this command and the matching #end as a
 //               callable subroutine to be invoked by a later #call
 //               callable subroutine to be invoked by a later #call
-//               command.
+//               command.  If is_defsub is false, it means this
+//               subroutine was actually defined via a #defun command,
+//               so it is to be invoked by a later variable reference,
+//               instead of by a #call command.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PPCommandFile::
 bool PPCommandFile::
-handle_defsub_command() {
-  // The only parameter of #defsub is the name of the subroutine.
-  string subroutine_name = trim_blanks(_scope->expand_string(_params));
+handle_defsub_command(bool is_defsub) {
+  string command = (is_defsub) ? "#defsub" : "#defun";
+
+  // The first word of the parameter list is the subroutine name; the
+  // rest is the comma-separated list of formal parameter names.
+
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string subroutine_name = trim_blanks(_params.substr(0, p));
 
 
   if (subroutine_name.empty()) {
   if (subroutine_name.empty()) {
-    cerr << "#defsub requires at least one parameter.\n";
+    cerr << command << " requires at least one parameter.\n";
     return false;
     return false;
   }
   }
 
 
+  vector<string> formals;
+  _scope->tokenize_params(_params.substr(p), formals, false);
+
+  vector<string>::const_iterator fi;
+  for (fi = formals.begin(); fi != formals.end(); ++fi) {
+    if (!is_valid_formal(*fi)) {
+      cerr << command << " " << subroutine_name
+	   << ": invalid formal parameter name '" << (*fi) << "'\n";
+      return false;
+    }
+  }
+
   if (_in_for) {
   if (_in_for) {
-    cerr << "#defsub may not appear within a #forscopes or #foreach block,\n"
-	 << "or within another #defsub block.\n";
+    cerr << command << " may not appear within another block scoping command like\n"
+	 << "#forscopes, #foreach, #formap, #defsub, or #defun.\n";
     return false;
     return false;
   }
   }
 
 
   BlockNesting *nest = new BlockNesting;
   BlockNesting *nest = new BlockNesting;
-  nest->_state = BS_defsub;
+  nest->_state = is_defsub ? BS_defsub : BS_defun;
   nest->_name = subroutine_name;
   nest->_name = subroutine_name;
   nest->_write_state = _write_state;
   nest->_write_state = _write_state;
   nest->_scope = _scope;
   nest->_scope = _scope;
   nest->_next = _block_nesting;
   nest->_next = _block_nesting;
+  nest->_words.swap(formals);
 
 
   _block_nesting = nest;
   _block_nesting = nest;
 
 
@@ -884,10 +1000,19 @@ handle_end_command() {
       return false;
       return false;
     }
     }
 
 
-  } else if (nest->_state == BS_defsub) {
+  } else if (nest->_state == BS_formap) {
+    // Now replay all of the saved lines.
+    _in_for = false;
+    assert(nest->_words.size() == 1);
+    if (!replay_formap(nest->_name, nest->_words[0])) {
+      return false;
+    }
+
+  } else if (nest->_state == BS_defsub || nest->_state == BS_defun) {
     // Save all of the saved lines as a named subroutine.
     // Save all of the saved lines as a named subroutine.
     _in_for = false;
     _in_for = false;
     PPSubroutine *sub = new PPSubroutine;
     PPSubroutine *sub = new PPSubroutine;
+    sub->_formals.swap(nest->_words);
     sub->_lines.swap(_saved_lines);
     sub->_lines.swap(_saved_lines);
 
 
     // Remove the #end command.  This will fail if someone makes an
     // Remove the #end command.  This will fail if someone makes an
@@ -895,7 +1020,11 @@ handle_end_command() {
     assert(!sub->_lines.empty());
     assert(!sub->_lines.empty());
     sub->_lines.pop_back();
     sub->_lines.pop_back();
 
 
-    PPSubroutine::define_sub(nest->_name, sub);
+    if (nest->_state == BS_defsub) {
+      PPSubroutine::define_sub(nest->_name, sub);
+    } else {
+      PPSubroutine::define_func(nest->_name, sub);
+    }
 
 
   } else if (nest->_state == BS_output) {
   } else if (nest->_state == BS_output) {
     if (!_in_for) {
     if (!_in_for) {
@@ -976,10 +1105,21 @@ handle_sinclude_command() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PPCommandFile::
 bool PPCommandFile::
 handle_call_command() {
 handle_call_command() {
-  string subroutine_name = trim_blanks(_scope->expand_string(_params));
+  // The first word is the name of the subroutine; the rest is the
+  // comma-separated list of expressions to substitute in for the
+  // subroutine's formal parameters.
+
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string subroutine_name = trim_blanks(_params.substr(0, p));
+  string params = _params.substr(p);
 
 
   if (subroutine_name.empty()) {
   if (subroutine_name.empty()) {
-    cerr << "#call with no parameter.\n";
+    cerr << "#call requires at least one parameter.\n";
+    return false;
   }
   }
 
 
   const PPSubroutine *sub = PPSubroutine::get_sub(subroutine_name);
   const PPSubroutine *sub = PPSubroutine::get_sub(subroutine_name);
@@ -987,13 +1127,23 @@ handle_call_command() {
     cerr << "Attempt to call undefined subroutine " << subroutine_name << "\n";
     cerr << "Attempt to call undefined subroutine " << subroutine_name << "\n";
   }
   }
 
 
+  PPScope *old_scope = _scope;
+  PPScope::push_scope(_scope);
+  PPScope nested_scope(_scope->get_named_scopes());
+  _scope = &nested_scope;
+  nested_scope.define_formals(subroutine_name, sub->_formals, params);
+
   vector<string>::const_iterator li;
   vector<string>::const_iterator li;
   for (li = sub->_lines.begin(); li != sub->_lines.end(); ++li) {
   for (li = sub->_lines.begin(); li != sub->_lines.end(); ++li) {
     if (!read_line(*li)) {
     if (!read_line(*li)) {
+      PPScope::pop_scope();
+      _scope = old_scope;
       return false;
       return false;
     }
     }
   }
   }
 
 
+  PPScope::pop_scope();
+  _scope = old_scope;
   return true;
   return true;
 }
 }
 
 
@@ -1005,8 +1155,10 @@ handle_call_command() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PPCommandFile::
 bool PPCommandFile::
 handle_error_command() {
 handle_error_command() {
-  if (!_params.empty()) {
-    cerr << _params << "\n";
+  string message = trim_blanks(_scope->expand_string(_params));
+  
+  if (!message.empty()) {
+    cerr << message << "\n";
   }
   }
   return false;
   return false;
 }
 }
@@ -1031,6 +1183,11 @@ handle_defer_command() {
     p++;
     p++;
   }
   }
   string varname = _params.substr(0, p);
   string varname = _params.substr(0, p);
+
+  if (PPSubroutine::get_func(varname) != (const PPSubroutine *)NULL) {
+    cerr << "Warning: variable " << varname
+	 << " shadowed by function definition.\n";
+  }
   
   
   // Skip whitespace between the variable name and its definition.
   // Skip whitespace between the variable name and its definition.
   while (p < _params.length() && isspace(_params[p])) {
   while (p < _params.length() && isspace(_params[p])) {
@@ -1065,6 +1222,11 @@ handle_define_command() {
     p++;
     p++;
   }
   }
   string varname = _params.substr(0, p);
   string varname = _params.substr(0, p);
+
+  if (PPSubroutine::get_func(varname) != (const PPSubroutine *)NULL) {
+    cerr << "Warning: variable " << varname
+	 << " shadowed by function definition.\n";
+  }
   
   
   // Skip whitespace between the variable name and its definition.
   // Skip whitespace between the variable name and its definition.
   while (p < _params.length() && isspace(_params[p])) {
   while (p < _params.length() && isspace(_params[p])) {
@@ -1099,6 +1261,11 @@ handle_set_command() {
     p++;
     p++;
   }
   }
   string varname = _params.substr(0, p);
   string varname = _params.substr(0, p);
+
+  if (PPSubroutine::get_func(varname) != (const PPSubroutine *)NULL) {
+    cerr << "Warning: variable " << varname
+	 << " shadowed by function definition.\n";
+  }
   
   
   // Skip whitespace between the variable name and its definition.
   // Skip whitespace between the variable name and its definition.
   while (p < _params.length() && isspace(_params[p])) {
   while (p < _params.length() && isspace(_params[p])) {
@@ -1136,18 +1303,37 @@ handle_map_command() {
   while (p < _params.length() && isspace(_params[p])) {
   while (p < _params.length() && isspace(_params[p])) {
     p++;
     p++;
   }
   }
-  string def = _params.substr(p);
-
-  // We don't expand the variable's definition immediately; it will be
-  // expanded when the variable is referenced later.  However, we
-  // should expand any simple self-reference immediately, to allow for
-  // recursive definitions.
-  def = _scope->expand_string(def);
+  string def = trim_blanks(_params.substr(p));
 
 
   _scope->define_map_variable(varname, def);
   _scope->define_map_variable(varname, def);
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_addmap_command
+//       Access: Protected
+//  Description: Handles the #addmap command: add a new key/scope pair
+//               to an existing map variable.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_addmap_command() {
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string varname = _params.substr(0, p);
+  
+  // Skip whitespace between the variable name and the key.
+  while (p < _params.length() && isspace(_params[p])) {
+    p++;
+  }
+  string key = trim_blanks(_scope->expand_string(_params.substr(p)));
+
+  _scope->add_to_map_variable(varname, key, _scope);
+  return true;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::include_file
 //     Function: PPCommandFile::include_file
@@ -1208,23 +1394,24 @@ replay_forscopes(const string &name) {
   vector<string> words;
   vector<string> words;
   tokenize_whitespace(name, words);
   tokenize_whitespace(name, words);
 
 
-  // Now traverse the named scopes with these names.
+  // Now build up the list of scopes with these names.
+  PPNamedScopes::Scopes scopes;
   vector<string>::const_iterator wi;
   vector<string>::const_iterator wi;
   for (wi = words.begin(); wi != words.end(); ++wi) {
   for (wi = words.begin(); wi != words.end(); ++wi) {
-    PPNamedScopes::Scopes scopes;
     named_scopes->get_scopes(*wi, scopes);
     named_scopes->get_scopes(*wi, scopes);
+  }
+  PPNamedScopes::sort_by_dependency(scopes);
     
     
-    PPNamedScopes::Scopes::const_iterator si;
-    for (si = scopes.begin(); si != scopes.end(); ++si) {
-      PPScope::push_scope(_scope);
-      _scope = (*si);
-      
-      vector<string>::const_iterator li;
-      for (li = lines.begin(); li != lines.end() && okflag; ++li) {
-	okflag = read_line(*li);
-      }
-      _scope = PPScope::pop_scope();
+  PPNamedScopes::Scopes::const_iterator si;
+  for (si = scopes.begin(); si != scopes.end(); ++si) {
+    PPScope::push_scope(_scope);
+    _scope = (*si);
+    
+    vector<string>::const_iterator li;
+    for (li = lines.begin(); li != lines.end() && okflag; ++li) {
+      okflag = read_line(*li);
     }
     }
+    _scope = PPScope::pop_scope();
   }
   }
 
 
   return okflag;
   return okflag;
@@ -1261,6 +1448,51 @@ replay_foreach(const string &varname, const vector<string> &words) {
   return okflag;
   return okflag;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::replay_formap
+//       Access: Protected
+//  Description: Replays all the lines that were saved during a
+//               previous #formap..#end block.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+replay_formap(const string &varname, const string &mapvar) {
+  bool okflag = true;
+
+  vector<string> lines;
+  lines.swap(_saved_lines);
+
+  // Remove the #end command.  This will fail if someone makes an #end
+  // command that spans multiple lines.  Don't do that.
+  assert(!lines.empty());
+  lines.pop_back();
+
+  // Now look up the map variable.
+  PPScope::MapVariableDefinition &def = _scope->find_map_variable(mapvar);
+  if (&def == &PPScope::_null_map_def) {
+    cerr << "Undefined map variable: #formap " << varname << " " 
+	 << mapvar << "\n";
+    return false;
+  }
+
+  // Now traverse through the map definition.
+  PPScope::MapVariableDefinition::const_iterator di;
+  for (di = def.begin(); di != def.end() && okflag; ++di) {
+    _scope->define_variable(varname, (*di).first);
+
+    PPScope::push_scope(_scope);
+    _scope = (*di).second;
+
+    vector<string>::const_iterator li;
+    for (li = lines.begin(); li != lines.end() && okflag; ++li) {
+      okflag = read_line(*li);
+    }
+
+    _scope = PPScope::pop_scope();
+  }
+
+  return okflag;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::compare_output
 //     Function: PPCommandFile::compare_output
 //       Access: Protected
 //       Access: Protected
@@ -1324,6 +1556,39 @@ failed_if() const {
 	  (_if_nesting->_state == IS_off || _if_nesting->_state == IS_done));
 	  (_if_nesting->_state == IS_off || _if_nesting->_state == IS_done));
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::is_valid_formal_parameter_name
+//       Access: Protected
+//  Description: Returns true if the indicated name is an acceptable
+//               name for a formal parameter.  This means it includes
+//               no whitespace or crazy punctuation.  Mainly this is
+//               to protect the user from making some stupid syntax
+//               mistake.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+is_valid_formal(const string &formal_parameter_name) const {
+  if (formal_parameter_name.empty()) {
+    return false;
+  }
+  
+  string::const_iterator si;
+  for (si = formal_parameter_name.begin();
+       si != formal_parameter_name.end();
+       ++si) {
+    switch (*si) {
+    case ' ':
+    case '\n':
+    case '\t':
+    case '$':
+    case '[':
+    case ']':
+    case ',':
+      return false;
+    }
+  }
+
+  return true;
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPCommandFile::PushFilename::Constructor
 //     Function: PPCommandFile::PushFilename::Constructor

+ 12 - 1
ppremake/ppCommandFile.h

@@ -24,6 +24,8 @@ public:
   PPCommandFile(PPScope *scope);
   PPCommandFile(PPScope *scope);
   ~PPCommandFile();
   ~PPCommandFile();
 
 
+  void set_output(ostream *out);
+
   void set_scope(PPScope *scope);
   void set_scope(PPScope *scope);
   PPScope *get_scope() const;
   PPScope *get_scope() const;
 
 
@@ -43,9 +45,11 @@ protected:
   bool handle_begin_command();
   bool handle_begin_command();
   bool handle_forscopes_command();
   bool handle_forscopes_command();
   bool handle_foreach_command();
   bool handle_foreach_command();
+  bool handle_formap_command();
   bool handle_format_command();
   bool handle_format_command();
   bool handle_output_command();
   bool handle_output_command();
-  bool handle_defsub_command();
+  bool handle_print_command();
+  bool handle_defsub_command(bool is_defsub);
   bool handle_end_command();
   bool handle_end_command();
 
 
   bool handle_include_command();
   bool handle_include_command();
@@ -57,13 +61,17 @@ protected:
   bool handle_define_command();
   bool handle_define_command();
   bool handle_set_command();
   bool handle_set_command();
   bool handle_map_command();
   bool handle_map_command();
+  bool handle_addmap_command();
 
 
   bool include_file(const string &filename);
   bool include_file(const string &filename);
   bool replay_forscopes(const string &name);
   bool replay_forscopes(const string &name);
   bool replay_foreach(const string &varname, const vector<string> &words);
   bool replay_foreach(const string &varname, const vector<string> &words);
+  bool replay_formap(const string &varname, const string &mapvar);
   bool compare_output(const string &temp_name, const string &true_name);
   bool compare_output(const string &temp_name, const string &true_name);
   bool failed_if() const;
   bool failed_if() const;
 
 
+  bool is_valid_formal(const string &formal_parameter_name) const;
+
 private:
 private:
   class PushFilename {
   class PushFilename {
   public:
   public:
@@ -98,7 +106,10 @@ private:
     BS_nested_forscopes,
     BS_nested_forscopes,
     BS_foreach,
     BS_foreach,
     BS_nested_foreach,
     BS_nested_foreach,
+    BS_formap,
+    BS_nested_formap,
     BS_defsub,
     BS_defsub,
+    BS_defun,
     BS_output
     BS_output
   };
   };
 
 

+ 17 - 7
ppremake/ppDirectoryTree.cxx

@@ -90,22 +90,32 @@ PPDirectoryTree(const string &dirname, PPDirectoryTree *parent) :
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: PPDirectoryTree::scan
+//     Function: PPDirectoryTree::scan_source
 //       Access: Public
 //       Access: Public
-//  Description: Reads in the complete hierarchy of source files.
-//               prefix is the pathname to the directory on disk,
-//               ending in slash.
+//  Description: Reads in the complete hierarchy of source files,
+//               beginning at the current directory.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PPDirectoryTree::
 bool PPDirectoryTree::
-scan(const string &prefix, PPNamedScopes *named_scopes) {
-  if (!r_scan(prefix)) {
+scan_source(PPNamedScopes *named_scopes) {
+  if (!r_scan("")) {
     return false;
     return false;
   }
   }
 
 
-  if (!read_source_file(prefix, named_scopes)) {
+  if (!read_source_file("", named_scopes)) {
     return false;
     return false;
   }
   }
 
 
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::scan_depends
+//       Access: Public
+//  Description: Reads in the depends file for each source file, and
+//               then sorts the files into dependency order.
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+scan_depends(PPNamedScopes *named_scopes) {
   if (!read_depends_file(named_scopes)) {
   if (!read_depends_file(named_scopes)) {
     return false;
     return false;
   }
   }

+ 2 - 1
ppremake/ppDirectoryTree.h

@@ -33,7 +33,8 @@ protected:
   PPDirectoryTree(const string &dirname, PPDirectoryTree *parent);
   PPDirectoryTree(const string &dirname, PPDirectoryTree *parent);
 
 
 public:
 public:
-  bool scan(const string &prefix, PPNamedScopes *named_scopes);
+  bool scan_source(PPNamedScopes *named_scopes);
+  bool scan_depends(PPNamedScopes *named_scopes);
 
 
   int count_source_files() const;
   int count_source_files() const;
 
 

+ 88 - 15
ppremake/ppMain.cxx

@@ -8,6 +8,9 @@
 #include "ppCommandFile.h"
 #include "ppCommandFile.h"
 
 
 #include <unistd.h>
 #include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h> // for perror
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPMain::Constructor
 //     Function: PPMain::Constructor
@@ -46,20 +49,17 @@ PPMain::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PPMain::
 bool PPMain::
 read_source(const string &root) {
 read_source(const string &root) {
-  // First, read the package file.  We find this either in this
-  // directory, or in some directory above us.
-  string search = root + "/";
-  if (root == ".") {
-    search = "";
-  }
+  // First, find the top of the source tree, as indicated by the
+  // presence of a Package.pp file.
+  string trydir = root;
 
 
-  string package_file = search + PACKAGE_FILENAME;
+  string package_file = trydir + "/" + PACKAGE_FILENAME;
 
 
   while (access(package_file.c_str(), F_OK) != 0) {
   while (access(package_file.c_str(), F_OK) != 0) {
     // We continue to walk up directories as long as we see a source
     // We continue to walk up directories as long as we see a source
     // file in each directory.  When we stop seeing source files, we
     // file in each directory.  When we stop seeing source files, we
     // stop walking upstairs.
     // stop walking upstairs.
-    string source_file = search + SOURCE_FILENAME;
+    string source_file = trydir + "/" + SOURCE_FILENAME;
     if (access(source_file.c_str(), F_OK) != 0) {
     if (access(source_file.c_str(), F_OK) != 0) {
       cerr << "Could not find ppremake package file " << PACKAGE_FILENAME
       cerr << "Could not find ppremake package file " << PACKAGE_FILENAME
 	   << ".\n\n"
 	   << ".\n\n"
@@ -69,24 +69,31 @@ read_source(const string &root) {
 	   << "important ppremake config files.\n\n";
 	   << "important ppremake config files.\n\n";
       return false;
       return false;
     }
     }
-    search += "../";
-    package_file = search + PACKAGE_FILENAME;
+    trydir += "/..";
+    package_file = trydir + "/" + PACKAGE_FILENAME;
   }
   }
 
 
+  // Now cd to the source root and get the actual path.
+  if (chdir(trydir.c_str()) < 0) {
+    perror("chdir");
+    return false;
+  }
+
+  string cwd = get_cwd();
+  cerr << "Root is " << cwd << "\n";
+
   _def_scope = new PPScope(&_named_scopes);
   _def_scope = new PPScope(&_named_scopes);
   _def_scope->define_variable("PACKAGEFILE", package_file);
   _def_scope->define_variable("PACKAGEFILE", package_file);
-  _def_scope->define_variable("TOPDIRPREFIX", search);
+  _def_scope->define_variable("TOPDIR", cwd);
   _defs = new PPCommandFile(_def_scope);
   _defs = new PPCommandFile(_def_scope);
 
 
-  //  cerr << "Reading " << package_file << "\n";
-  if (!_defs->read_file(package_file)) {
-    cerr << "Error reading package file " << package_file << ".\n";
+  if (!_defs->read_file(PACKAGE_FILENAME)) {
     return false;
     return false;
   }
   }
 
 
   PPScope::push_scope(_def_scope);
   PPScope::push_scope(_def_scope);
 
 
-  if (!_tree.scan(search, &_named_scopes)) {
+  if (!_tree.scan_source(&_named_scopes)) {
     return false;
     return false;
   }
   }
 
 
@@ -102,6 +109,17 @@ read_source(const string &root) {
     return false;
     return false;
   }
   }
 
 
+  cerr << "Read " << _tree.count_source_files() << " " << SOURCE_FILENAME
+       << " files.\n";
+
+  if (!read_global_file()) {
+    return false;
+  }
+
+  if (!_tree.scan_depends(&_named_scopes)) {
+    return false;
+  }
+
   return true;
   return true;
 }
 }
 
 
@@ -191,3 +209,58 @@ p_process(PPDirectoryTree *dir) {
 
 
   return true;
   return true;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::read_global_file
+//       Access: Private
+//  Description: Reads in the Global.pp file after all sources files
+//               have been read and sorted into dependency order.
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+read_global_file() {
+  assert(_def_scope != (PPScope *)NULL);
+
+  string global_filename = _def_scope->expand_variable("GLOBAL_FILE");
+  if (global_filename.empty()) {
+    cerr << "No definition given for $[GLOBAL_FILE], cannot process.\n";
+    return false;
+  }
+  
+  PPCommandFile global(_def_scope);
+  if (!global.read_file(global_filename)) {
+    cerr << "Error reading global definition file "
+	 << global_filename << ".\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::get_cwd
+//       Access: Private, Static
+//  Description: Calls the system getcwd(), automatically allocating a
+//               large enough string.
+////////////////////////////////////////////////////////////////////
+string PPMain::
+get_cwd() {
+  static size_t bufsize = 1024;
+  static char *buffer = NULL;
+
+  if (buffer == (char *)NULL) {
+    buffer = new char[bufsize];
+  }
+
+  while (getcwd(buffer, bufsize) == (char *)NULL) {
+    if (errno != ERANGE) {
+      perror("getcwd");
+      return string();
+    }
+    delete[] buffer;
+    bufsize = bufsize * 2;
+    buffer = new char[bufsize];
+    assert(buffer != (char *)NULL);
+  }
+
+  return string(buffer);
+}

+ 2 - 0
ppremake/ppMain.h

@@ -32,6 +32,8 @@ public:
 private:
 private:
   bool r_process_all(PPDirectoryTree *dir);
   bool r_process_all(PPDirectoryTree *dir);
   bool p_process(PPDirectoryTree *dir);
   bool p_process(PPDirectoryTree *dir);
+  bool read_global_file();
+  static string get_cwd();
 
 
 
 
   PPScope *_global_scope;
   PPScope *_global_scope;

+ 49 - 0
ppremake/ppNamedScopes.cxx

@@ -5,6 +5,43 @@
 
 
 #include "ppNamedScopes.h"
 #include "ppNamedScopes.h"
 #include "ppScope.h"
 #include "ppScope.h"
+#include "ppDirectoryTree.h"
+
+#include <assert.h>
+#include <algorithm>
+
+// An STL object to sort named scopes in order by dependency and then
+// by directory name, used in sort_by_dependency().
+class SortScopesByDependencyAndName {
+public:
+  bool operator () (const PPScope *a, const PPScope *b) const {
+    PPDirectoryTree *da = a->get_directory();
+    PPDirectoryTree *db = b->get_directory();
+
+    // Scopes without associated directories appear first in the list.
+    bool da_is_null = (da == (PPDirectoryTree *)NULL);
+    bool db_is_null = (db == (PPDirectoryTree *)NULL);
+
+    if (da_is_null != db_is_null) {
+      return da_is_null > db_is_null;
+
+    } else if (da_is_null) {
+      // If two scopes have no associated directories (!) they are
+      // considered equivalent.
+      return false;
+
+    } else {
+      // Otherwise, both scopes have associated directories, and we
+      // can properly put them in order by dependencies.
+      assert(da != (PPDirectoryTree *)NULL);
+      assert(db != (PPDirectoryTree *)NULL);
+      if (da->get_depends_index() != db->get_depends_index()) {
+	return da->get_depends_index() < db->get_depends_index();
+      }
+      return da->get_dirname() < db->get_dirname();
+    }
+  }
+};
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPNamedScopes::Constructor
 //     Function: PPNamedScopes::Constructor
@@ -83,6 +120,18 @@ get_scopes(const string &name, Scopes &scopes) const {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::sort_by_dependency
+//       Access: Public, Static
+//  Description: Sorts the previously-generated list of scopes into
+//               order such that the later scopes depend on the
+//               earlier scopes.
+////////////////////////////////////////////////////////////////////
+void PPNamedScopes::
+sort_by_dependency(PPNamedScopes::Scopes &scopes) {
+  sort(scopes.begin(), scopes.end(), SortScopesByDependencyAndName());
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPNamedScopes::set_current
 //     Function: PPNamedScopes::set_current
 //       Access: Public
 //       Access: Public

+ 1 - 0
ppremake/ppNamedScopes.h

@@ -29,6 +29,7 @@ public:
 
 
   PPScope *make_scope(const string &name);
   PPScope *make_scope(const string &name);
   void get_scopes(const string &name, Scopes &scopes) const;
   void get_scopes(const string &name, Scopes &scopes) const;
+  static void sort_by_dependency(Scopes &scopes);
 
 
   void set_current(const string &dirname);
   void set_current(const string &dirname);
 
 

+ 450 - 114
ppremake/ppScope.cxx

@@ -7,6 +7,8 @@
 #include "ppNamedScopes.h"
 #include "ppNamedScopes.h"
 #include "ppFilenamePattern.h"
 #include "ppFilenamePattern.h"
 #include "ppDirectoryTree.h"
 #include "ppDirectoryTree.h"
+#include "ppSubroutine.h"
+#include "ppCommandFile.h"
 #include "tokenize.h"
 #include "tokenize.h"
 #include "find_searchpath.h"
 #include "find_searchpath.h"
 
 
@@ -16,7 +18,7 @@
 #include <glob.h>
 #include <glob.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <unistd.h>
-#include <stdio.h>  // for perror().
+#include <stdio.h>  // for perror() and sprintf().
 #include <errno.h>
 #include <errno.h>
 #include <signal.h>
 #include <signal.h>
 #include <sys/types.h>
 #include <sys/types.h>
@@ -175,6 +177,10 @@ define_map_variable(const string &varname, const string &key_varname,
     return;
     return;
   }
   }
 
 
+  if (key_varname.empty()) {
+    return;
+  }
+
   vector<string> names;
   vector<string> names;
   tokenize_whitespace(scope_names, names);
   tokenize_whitespace(scope_names, names);
 
 
@@ -214,6 +220,71 @@ define_map_variable(const string &varname, const string &key_varname,
   define_variable(varname, repaste(results, " "));
   define_variable(varname, repaste(results, " "));
 }
 }
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::add_to_map_variable
+//       Access: Public
+//  Description: Adds a new key/scope pair to a previous map variable
+//               definition.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+add_to_map_variable(const string &varname, const string &key,
+		    PPScope *scope) {
+  MapVariableDefinition &def = find_map_variable(varname);
+  if (&def == &_null_map_def) {
+    cerr << "Warning:  undefined map variable: " << varname << "\n";
+    return;
+  }
+
+  def[key] = scope;
+
+  // We need to do all this work to define the traditional expansion.
+  // Maybe not a great idea.
+  vector<string> results;
+  MapVariableDefinition::const_iterator di;
+  for (di = def.begin(); di != def.end(); ++di) {
+    results.push_back((*di).first);
+  }
+
+  set_variable(varname, repaste(results, " "));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::define_formals
+//       Access: Public
+//  Description: Supplies values to a slew of variables at once,
+//               typically to define actual values for a list of
+//               formal parameters to a user-defined subroutine or
+//               function.
+//
+//               Formals is a vector of variable names to be defined,
+//               and actuals is a comma-separated list of expressions
+//               to be substituted in, one-per-one.  The
+//               subroutine_name is used only for error reporting.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+define_formals(const string &subroutine_name, 
+	       const vector<string> &formals, const string &actuals) {
+  vector<string> actual_words;
+  tokenize_params(actuals, actual_words, true);
+
+  if (actual_words.size() < formals.size()) {
+    cerr << "Warning: not all parameters defined for " << subroutine_name
+	 << ": " << actuals << "\n";
+  } else if (actual_words.size() > formals.size()) {
+    cerr << "Warning: more parameters defined for " << subroutine_name
+	 << " than actually exist: " << actuals << "\n";
+  }
+
+  for (int i = 0; i < (int)formals.size(); i++) {
+    if (i < (int)actual_words.size()) {
+      define_variable(formals[i], actual_words[i]);
+    } else {
+      define_variable(formals[i], string());
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::get_variable
 //     Function: PPScope::get_variable
 //       Access: Public
 //       Access: Public
@@ -222,6 +293,12 @@ define_map_variable(const string &varname, const string &key_varname,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 string PPScope::
 string PPScope::
 get_variable(const string &varname) const {
 get_variable(const string &varname) const {
+  // Is it a user-defined function?
+  const PPSubroutine *sub = PPSubroutine::get_func(varname);
+  if (sub != (const PPSubroutine *)NULL) {
+    return expand_function(varname, sub, string());
+  }      
+
   string result;
   string result;
   if (p_get_variable(varname, result)) {
   if (p_get_variable(varname, result)) {
     return result;
     return result;
@@ -256,6 +333,34 @@ expand_variable(const string &varname) const {
   return expand_string(get_variable(varname));
   return expand_string(get_variable(varname));
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::find_map_variable
+//       Access: Public
+//  Description: Looks for the map variable definition in this scope
+//               or some ancestor scope.  Returns the map variable
+//               definition if it is found, or _null_map_def if it is
+//               not.
+////////////////////////////////////////////////////////////////////
+PPScope::MapVariableDefinition &PPScope::
+find_map_variable(const string &varname) const {
+  MapVariableDefinition &def = p_find_map_variable(varname);
+  if (&def != &_null_map_def) {
+    return def;
+  }
+
+  // No such map variable.  Check the stack.
+  ScopeStack::reverse_iterator si;
+  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
+    MapVariableDefinition &def = (*si)->p_find_map_variable(varname);
+    if (&def != &_null_map_def) {
+      return def;
+    }
+  }
+
+  // Nada.
+  return _null_map_def;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::get_directory
 //     Function: PPScope::get_directory
 //       Access: Public
 //       Access: Public
@@ -384,6 +489,62 @@ get_bottom_scope() {
   return _scope_stack.front();
   return _scope_stack.front();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::tokenize_params
+//       Access: Public
+//  Description: Separates a string into tokens based on comma
+//               delimiters, e.g. for parameters to a function.
+//               Nested variable references are skipped correctly,
+//               even if they include commas.  Leading and trailing
+//               whitespace in each token is automatically stripped.
+//
+//               If expand is true, the nested variables are
+//               automatically expanded as the string is tokenized;
+//               otherwise, they are left unexpanded.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+tokenize_params(const string &str, vector<string> &tokens,
+		bool expand) const {
+  size_t p = 0;
+  while (p < str.length()) {
+    // Skip initial whitespace.
+    while (p < str.length() && isspace(str[p])) {
+      p++;
+    }
+
+    string token;
+    while (p < str.length() && str[p] != FUNCTION_PARAMETER_SEPARATOR) {
+      if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX &&
+	  str[p + 1] == VARIABLE_OPEN_BRACE) {
+	// Skip a nested variable reference.
+	if (expand) {
+	  token += r_expand_variable(str, p, (ExpandedVariable *)NULL);
+	} else {
+	  token += r_scan_variable(str, p);
+	}
+      } else {
+	token += str[p];
+	p++;
+      }
+    }
+
+    // Back up past trailing whitespace.
+    size_t q = token.length();
+    while (q > 0 && isspace(token[q - 1])) {
+      q--;
+    }
+
+    tokens.push_back(token.substr(0, q));
+    p++;
+
+    if (p == str.length()) {
+      // In this case, we have just read past a trailing comma symbol
+      // at the end of the string, so we have one more empty token.
+      tokens.push_back(string());
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::p_set_variable
 //     Function: PPScope::p_set_variable
 //       Access: Private
 //       Access: Private
@@ -434,6 +595,18 @@ p_get_variable(const string &varname, string &result) const {
     return true;
     return true;
   }
   }
 
 
+  if (varname == "DEPENDS_INDEX" && 
+      _directory != (PPDirectoryTree *)NULL) {
+    // $[DEPENDS_INDEX] is another special variable name that
+    // evaluates to the numeric sorting index assigned to this
+    // directory based on its dependency relationship with other
+    // directories.  It's useful primarily for debugging.
+    char buffer[32];
+    sprintf(buffer, "%d", _directory->get_depends_index());
+    result = buffer;
+    return true;
+  }
+
   if (_parent_scope != (PPScope *)NULL) {
   if (_parent_scope != (PPScope *)NULL) {
     return _parent_scope->p_get_variable(varname, result);
     return _parent_scope->p_get_variable(varname, result);
   }
   }
@@ -441,62 +614,6 @@ p_get_variable(const string &varname, string &result) const {
   return false;
   return false;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: PPScope::tokenize_params
-//       Access: Private
-//  Description: Separates a string into tokens based on comma
-//               delimiters, e.g. for parameters to a function.
-//               Nested variable references are skipped correctly,
-//               even if they include commas.  Leading and trailing
-//               whitespace in each token is automatically stripped.
-//
-//               If expand is true, the nested variables are
-//               automatically expanded as the string is tokenized;
-//               otherwise, they are left unexpanded.
-////////////////////////////////////////////////////////////////////
-void PPScope::
-tokenize_params(const string &str, vector<string> &tokens,
-		bool expand) const {
-  size_t p = 0;
-  while (p < str.length()) {
-    // Skip initial whitespace.
-    while (p < str.length() && isspace(str[p])) {
-      p++;
-    }
-
-    string token;
-    while (p < str.length() && str[p] != FUNCTION_PARAMETER_SEPARATOR) {
-      if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX &&
-	  str[p + 1] == VARIABLE_OPEN_BRACE) {
-	// Skip a nested variable reference.
-	if (expand) {
-	  token += r_expand_variable(str, p, (ExpandedVariable *)NULL);
-	} else {
-	  token += r_scan_variable(str, p);
-	}
-      } else {
-	token += str[p];
-	p++;
-      }
-    }
-
-    // Back up past trailing whitespace.
-    size_t q = token.length();
-    while (q > 0 && isspace(token[q - 1])) {
-      q--;
-    }
-
-    tokens.push_back(token.substr(0, q));
-    p++;
-
-    if (p == str.length()) {
-      // In this case, we have just read past a trailing comma symbol
-      // at the end of the string, so we have one more empty token.
-      tokens.push_back(string());
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::r_expand_string
 //     Function: PPScope::r_expand_string
 //       Access: Private
 //       Access: Private
@@ -639,18 +756,27 @@ r_expand_variable(const string &str, size_t &vp,
     }
     }
     string params = varname.substr(p);
     string params = varname.substr(p);
 
 
-    // Is it a built-in function?
+    // Is it a user-defined function?
+    const PPSubroutine *sub = PPSubroutine::get_func(funcname);
+    if (sub != (const PPSubroutine *)NULL) {
+      return expand_function(funcname, sub, params);
+    }      
 
 
+    // Is it a built-in function?
     if (funcname == "wildcard") {
     if (funcname == "wildcard") {
       return expand_wildcard(params);
       return expand_wildcard(params);
     } else if (funcname == "isdir") {
     } else if (funcname == "isdir") {
       return expand_isdir(params);
       return expand_isdir(params);
+    } else if (funcname == "isfile") {
+      return expand_isfile(params);
     } else if (funcname == "libtest") {
     } else if (funcname == "libtest") {
       return expand_libtest(params);
       return expand_libtest(params);
     } else if (funcname == "bintest") {
     } else if (funcname == "bintest") {
       return expand_bintest(params);
       return expand_bintest(params);
     } else if (funcname == "shell") {
     } else if (funcname == "shell") {
       return expand_shell(params);
       return expand_shell(params);
+    } else if (funcname == "standardize") {
+      return expand_standardize(params);
     } else if (funcname == "firstword") {
     } else if (funcname == "firstword") {
       return expand_firstword(params);
       return expand_firstword(params);
     } else if (funcname == "patsubst") {
     } else if (funcname == "patsubst") {
@@ -681,8 +807,12 @@ r_expand_variable(const string &str, size_t &vp,
       return expand_upcase(params);
       return expand_upcase(params);
     } else if (funcname == "downcase") {
     } else if (funcname == "downcase") {
       return expand_downcase(params);
       return expand_downcase(params);
+    } else if (funcname == "cdefine") {
+      return expand_cdefine(params);
     } else if (funcname == "closure") {
     } else if (funcname == "closure") {
       return expand_closure(params);
       return expand_closure(params);
+    } else if (funcname == "unmapped") {
+      return expand_unmapped(params);
     }
     }
 
 
     // It must be a map variable.
     // It must be a map variable.
@@ -700,6 +830,7 @@ r_expand_variable(const string &str, size_t &vp,
   }
   }
 
 
   // And now expand the variable.
   // And now expand the variable.
+
   string expansion;
   string expansion;
 
 
   // Check for a special inline patsubst operation, like GNU make:
   // Check for a special inline patsubst operation, like GNU make:
@@ -866,6 +997,39 @@ expand_isdir(const string &params) const {
   return result;
   return result;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_isfile
+//       Access: Private
+//  Description: Expands the "isfile" function variable.  This
+//               returns true if the parameter exists and is a
+//               regular file, or false otherwise.  This actually
+//               expands the parameter(s) with shell globbing
+//               characters, similar to the "wildcard" function, and
+//               looks only at the first expansion.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_isfile(const string &params) const {
+  vector<string> results;
+  glob_string(expand_string(params), results);
+
+  if (results.empty()) {
+    // No matching file, too bad.
+    return string();
+  }
+
+  const string &filename = results[0];
+  struct stat stbuf;
+
+  string result;
+  if (stat(filename.c_str(), &stbuf) == 0) {
+    if (S_ISREG(stbuf.st_mode)) {
+      result = filename;
+    }
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::expand_libtest
 //     Function: PPScope::expand_libtest
 //       Access: Private
 //       Access: Private
@@ -1109,6 +1273,64 @@ expand_shell(const string &params) const {
 }
 }
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_standardize
+//       Access: Private
+//  Description: Expands the "standardize" function variable.  This
+//               converts the filename to standard form by removing
+//               consecutive repeated slashes and collapsing /../
+//               where possible.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_standardize(const string &params) const {
+  string filename = expand_string(params);
+  if (filename.empty()) {
+    return string();
+  }
+
+  vector<string> components;
+
+  // Pull off the components of the filename one at a time.
+  bool global = (filename[0] == '/');
+
+  size_t p = 0;
+  while (p < filename.length() && filename[p] == '/') {
+    p++;
+  }
+  while (p < filename.length()) {
+    size_t slash = filename.find('/', p);
+    string component = filename.substr(p, slash - p);
+    if (component == ".") {
+      // Ignore /./.
+    } else if (component == ".." && !components.empty() && 
+	       !(components.back() == "..")) {
+      // Back up.
+      components.pop_back();
+    } else {
+      components.push_back(component);
+    }
+
+    p = slash;
+    while (p < filename.length() && filename[p] == '/') {
+      p++;
+    }
+  }
+   
+  // Now reassemble the filename.
+  string result;
+  if (global) {
+    result = "/";
+  }
+  if (!components.empty()) {
+    result += components[0];
+    for (int i = 1; i < (int)components.size(); i++) {
+      result += "/" + components[i];
+    }
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::expand_firstword
 //     Function: PPScope::expand_firstword
 //       Access: Private
 //       Access: Private
@@ -1408,9 +1630,13 @@ expand_unique(const string &params) const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: PPScope::expand_eq
+//     Function: PPScope::expand_if
 //       Access: Private
 //       Access: Private
-//  Description: Expands the "if" function variable.
+//  Description: Expands the "if" function variable.  This evaluates
+//               the first parameter and returns the second parameter
+//               if the result is true (i.e. nonempty) and the third
+//               parameter (if present) if the result is faluse
+//               (i.e. empty).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 string PPScope::
 string PPScope::
 expand_if(const string &params) const {
 expand_if(const string &params) const {
@@ -1424,7 +1650,7 @@ expand_if(const string &params) const {
     } else {
     } else {
       return "";
       return "";
     }
     }
-  } else if (tokens.size() == 2) {
+  } else if (tokens.size() == 3) {
     if (!tokens[0].empty()) {
     if (!tokens[0].empty()) {
       return tokens[1];
       return tokens[1];
     } else {
     } else {
@@ -1515,6 +1741,7 @@ expand_not(const string &params) const {
 //       Access: Private
 //       Access: Private
 //  Description: Expands the "or" function variable.  This returns
 //  Description: Expands the "or" function variable.  This returns
 //               nonempty if any of its arguments are nonempty.
 //               nonempty if any of its arguments are nonempty.
+//               Specifically, it returns the first nonempty argument.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 string PPScope::
 string PPScope::
 expand_or(const string &params) const {
 expand_or(const string &params) const {
@@ -1525,10 +1752,10 @@ expand_or(const string &params) const {
   vector<string>::const_iterator ti;
   vector<string>::const_iterator ti;
   for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
   for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
     if (!(*ti).empty()) {
     if (!(*ti).empty()) {
-      return "1";
+      return (*ti);
     }
     }
   }
   }
-  return "";
+  return string();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1536,6 +1763,7 @@ expand_or(const string &params) const {
 //       Access: Private
 //       Access: Private
 //  Description: Expands the "and" function variable.  This returns
 //  Description: Expands the "and" function variable.  This returns
 //               nonempty if all of its arguments are nonempty.
 //               nonempty if all of its arguments are nonempty.
+//               Specifically, it returns the last argument.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 string PPScope::
 string PPScope::
 expand_and(const string &params) const {
 expand_and(const string &params) const {
@@ -1546,10 +1774,15 @@ expand_and(const string &params) const {
   vector<string>::const_iterator ti;
   vector<string>::const_iterator ti;
   for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
   for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
     if ((*ti).empty()) {
     if ((*ti).empty()) {
-      return "";
+      return string();
     }
     }
   }
   }
-  return "1";
+
+  if (tokens.empty()) {
+    return "1";
+  } else {
+    return tokens.back();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1582,6 +1815,33 @@ expand_downcase(const string &params) const {
   return result;
   return result;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_cdefine
+//       Access: Private
+//  Description: Expands the "cdefine" function variable.  This is a
+//               convenience function to output a C-style #define or
+//               #undef statement based on the value of the named
+//               variable.  If the named string is a variable whose
+//               definition is nonempty, this returns "#define varname
+//               definition".  Otherwise, it returns "#undef varname".
+//               This is particularly useful for building up a
+//               config.h file.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_cdefine(const string &params) const {
+  string varname = trim_blanks(params);
+  string expansion = trim_blanks(expand_variable(varname));
+
+  string result;
+  if (expansion.empty()) {
+    result = "#undef " + varname;
+  } else {
+    result = "#define " + varname + " " + expansion;
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::expand_closure
 //     Function: PPScope::expand_closure
 //       Access: Private
 //       Access: Private
@@ -1596,15 +1856,21 @@ expand_closure(const string &params) const {
   vector<string> tokens;
   vector<string> tokens;
   tokenize_params(params, tokens, false);
   tokenize_params(params, tokens, false);
 
 
-  if (tokens.size() != 2) {
-    cerr << "closure requires two parameters.\n";
+  if (tokens.size() != 2 && tokens.size() != 3) {
+    cerr << "closure requires two or three parameters.\n";
     return string();
     return string();
   }
   }
 
 
   // The first parameter is the map variable name, the second
   // The first parameter is the map variable name, the second
-  // parameter is the expression to close.
+  // parameter is the expression to evaluate, and the third parameter
+  // (if present) is the expression that leads to the recursive
+  // evaluation of the map variable.
   string varname = expand_string(tokens[0]);
   string varname = expand_string(tokens[0]);
   string expression = tokens[1];
   string expression = tokens[1];
+  string close_on = expression;
+  if (tokens.size() > 2) {
+    close_on = tokens[2];
+  }
 
 
   const MapVariableDefinition &def = find_map_variable(varname);
   const MapVariableDefinition &def = find_map_variable(varname);
   if (&def == &_null_map_def) {
   if (&def == &_null_map_def) {
@@ -1619,16 +1885,21 @@ expand_closure(const string &params) const {
   // we also need to keep track of all the partial results we have yet
   // we also need to keep track of all the partial results we have yet
   // to evaluate (hence the vector of strings).
   // to evaluate (hence the vector of strings).
   set<string> closure;
   set<string> closure;
-  vector<string> partial_results;
+  vector<string> results;
+  vector<string> next_pass;
+
+  // Start off with the expression evaluated within the starting
+  // scope.
+  results.push_back(expand_string(expression));
 
 
-  partial_results.push_back(expand_string(expression));
+  next_pass.push_back(expand_string(close_on));
 
 
-  while (!partial_results.empty()) {
+  while (!next_pass.empty()) {
     // Pull off one of the partial results (it doesn't matter which
     // Pull off one of the partial results (it doesn't matter which
     // one), and chop it up into its constituent words.
     // one), and chop it up into its constituent words.
     vector<string> pass;
     vector<string> pass;
-    tokenize_whitespace(partial_results.back(), pass);
-    partial_results.pop_back();
+    tokenize_whitespace(next_pass.back(), pass);
+    next_pass.pop_back();
 
 
     // And then map each of those words into scopes.
     // And then map each of those words into scopes.
     vector<string>::const_iterator wi;
     vector<string>::const_iterator wi;
@@ -1636,30 +1907,121 @@ expand_closure(const string &params) const {
       const string &word = (*wi);
       const string &word = (*wi);
       bool inserted = closure.insert(word).second;
       bool inserted = closure.insert(word).second;
       if (inserted) {
       if (inserted) {
-	// This is a new word, which presumably maps to a scope.  What
-	// does the expression evaluate to within that scope?
-	
-	MapVariableDefinition::const_iterator mvi;
-	mvi = def.find(word);
-	if (mvi != def.end()) {
-	  PPScope *scope = (*mvi).second;
-	  partial_results.push_back(scope->expand_string(expression));
+	// This is a new word, which presumably maps to a scope.
+	MapVariableDefinition::const_iterator di;
+	di = def.find(word);
+	if (di != def.end()) {
+	  PPScope *scope = (*di).second;
+	  // Evaluate the expression within this scope.
+	  results.push_back(scope->expand_string(expression));
+	  
+	  // What does close_on evaluate to within this scope?  That
+	  // points us to the next scope(s).
+	  next_pass.push_back(scope->expand_string(close_on));
 	}
 	}
       }
       }
     }
     }
   }
   }
 
 
-  // Now we have the complete transitive closure of $[mapvar expression].
+  // Now we have the complete transitive closure of $[mapvar close_on].
+  string result = repaste(results, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_unmapped
+//       Access: Private
+//  Description: Expands the "closure" function variable.  This is a
+//               special function that returns all the arguments to a
+//               map variable, unchanged, that did *not* match any of
+//               the keys in the map.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_unmapped(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, false);
+
+  if (tokens.size() != 2) {
+    cerr << "unmapped requires two parameters.\n";
+    return string();
+  }
+
+  // The first parameter is the map variable name, and the second
+  // parameter is the space-separated list of arguments to the map.
+  string varname = expand_string(tokens[0]);
+  vector<string> keys;
+  tokenize_whitespace(expand_string(tokens[1]), keys);
+
+  const MapVariableDefinition &def = find_map_variable(varname);
+  if (&def == &_null_map_def) {
+    cerr << "Warning:  undefined map variable: " << varname << "\n";
+    return string();
+  }
+
   vector<string> results;
   vector<string> results;
-  set<string>::const_iterator ci;
-  for (ci = closure.begin(); ci != closure.end(); ++ci) {
-    results.push_back(*ci);
+  vector<string>::const_iterator ki;
+  for (ki = keys.begin(); ki != keys.end(); ++ki) {
+    MapVariableDefinition::const_iterator di;
+    di = def.find(*ki);
+    if (di == def.end()) {
+      // This key was undefined.
+      results.push_back(*ki);
+    }
   }
   }
 
 
   string result = repaste(results, " ");
   string result = repaste(results, " ");
   return result;
   return result;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_function
+//       Access: Private
+//  Description: Expands the user-defined function reference.  This
+//               invokes the nested commands within the function body,
+//               and returns all the output text as one line.  Quite a
+//               job, really.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_function(const string &funcname, 
+		const PPSubroutine *sub, const string &params) const {
+  PPScope::push_scope((PPScope *)this);
+  PPScope nested_scope(_named_scopes);
+  nested_scope.define_formals(funcname, sub->_formals, params);
+
+  // This won't compile on VC++.  It has only ostringstream, which is
+  // functionally equivalent but has a slightly different interface.
+  ostrstream ostr;
+
+  PPCommandFile command(&nested_scope);
+  command.set_output(&ostr);
+
+  command.begin_read();
+  bool okflag = true;
+  vector<string>::const_iterator li;
+  for (li = sub->_lines.begin(); li != sub->_lines.end() && okflag; ++li) {
+    okflag = command.read_line(*li);
+  }
+  if (okflag) {
+    okflag = command.end_read();
+  }
+
+  PPScope::pop_scope();
+
+  // Now get the output.  We split it into words and then reconnect
+  // it, to replace all whitespace with spaces.
+  ostr << ends;
+  char *str = ostr.str();
+
+  vector<string> results;
+  tokenize_whitespace(str, results);
+
+  string result = repaste(results, " ");
+  delete[] str;
+  
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::expand_map_variable
 //     Function: PPScope::expand_map_variable
 //       Access: Private
 //       Access: Private
@@ -1725,48 +2087,22 @@ expand_map_variable(const string &varname, const string &expression,
   return result;
   return result;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: PPScope::find_map_variable
-//       Access: Private
-//  Description: Looks for the map variable definition in this scope
-//               or some ancestor scope.
-////////////////////////////////////////////////////////////////////
-const PPScope::MapVariableDefinition &PPScope::
-find_map_variable(const string &varname) const {
-  const MapVariableDefinition &def = p_find_map_variable(varname);
-  if (&def != &_null_map_def) {
-    return def;
-  }
-
-  // No such map variable.  Check the stack.
-  ScopeStack::reverse_iterator si;
-  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
-    const MapVariableDefinition &def = (*si)->p_find_map_variable(varname);
-    if (&def != &_null_map_def) {
-      return def;
-    }
-  }
-
-  // Nada.
-  return _null_map_def;
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPScope::p_find_map_variable
 //     Function: PPScope::p_find_map_variable
 //       Access: Private
 //       Access: Private
 //  Description: The implementation of find_map_variable() for a
 //  Description: The implementation of find_map_variable() for a
 //               particular static scope, without checking the stack.
 //               particular static scope, without checking the stack.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-const PPScope::MapVariableDefinition &PPScope::
+PPScope::MapVariableDefinition &PPScope::
 p_find_map_variable(const string &varname) const {
 p_find_map_variable(const string &varname) const {
   MapVariables::const_iterator mvi;
   MapVariables::const_iterator mvi;
   mvi = _map_variables.find(varname);
   mvi = _map_variables.find(varname);
   if (mvi != _map_variables.end()) {
   if (mvi != _map_variables.end()) {
-    return (*mvi).second;
+    return (MapVariableDefinition &)(*mvi).second;
   }
   }
 
 
   if (_parent_scope != (PPScope *)NULL) {
   if (_parent_scope != (PPScope *)NULL) {
-    return _parent_scope->find_map_variable(varname);
+    return _parent_scope->p_find_map_variable(varname);
   }
   }
 
 
   return _null_map_def;
   return _null_map_def;

+ 20 - 9
ppremake/ppScope.h

@@ -13,6 +13,7 @@
 
 
 class PPNamedScopes;
 class PPNamedScopes;
 class PPDirectoryTree;
 class PPDirectoryTree;
+class PPSubroutine;
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
 // 	 Class : PPScope
 // 	 Class : PPScope
@@ -23,6 +24,8 @@ class PPDirectoryTree;
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class PPScope {
 class PPScope {
 public:
 public:
+  typedef map<string, PPScope *> MapVariableDefinition;
+
   PPScope(PPNamedScopes *named_scopes);
   PPScope(PPNamedScopes *named_scopes);
 
 
   PPNamedScopes *get_named_scopes() const;
   PPNamedScopes *get_named_scopes() const;
@@ -35,9 +38,14 @@ public:
   void define_map_variable(const string &varname, const string &definition);
   void define_map_variable(const string &varname, const string &definition);
   void define_map_variable(const string &varname, const string &key_varname,
   void define_map_variable(const string &varname, const string &key_varname,
 			   const string &scope_names);
 			   const string &scope_names);
+  void add_to_map_variable(const string &varname, const string &key,
+			   PPScope *scope);
+  void define_formals(const string &subroutine_name,
+		      const vector<string> &formals, const string &actuals);
 
 
   string get_variable(const string &varname) const;
   string get_variable(const string &varname) const;
   string expand_variable(const string &varname) const;
   string expand_variable(const string &varname) const;
+  MapVariableDefinition &find_map_variable(const string &varname) const;
 
 
   PPDirectoryTree *get_directory() const;
   PPDirectoryTree *get_directory() const;
   void set_directory(PPDirectoryTree *directory);
   void set_directory(PPDirectoryTree *directory);
@@ -49,6 +57,11 @@ public:
   static PPScope *pop_scope();
   static PPScope *pop_scope();
   static PPScope *get_bottom_scope();
   static PPScope *get_bottom_scope();
 
 
+  void tokenize_params(const string &str, vector<string> &tokens,
+		       bool expand) const;
+
+  static MapVariableDefinition _null_map_def;
+
 private:
 private:
   class ExpandedVariable {
   class ExpandedVariable {
   public:
   public:
@@ -56,14 +69,9 @@ private:
     ExpandedVariable *_next;
     ExpandedVariable *_next;
   };
   };
 
 
-  typedef map<string, PPScope *> MapVariableDefinition;
-
   bool p_set_variable(const string &varname, const string &definition);
   bool p_set_variable(const string &varname, const string &definition);
   bool p_get_variable(const string &varname, string &result) const;
   bool p_get_variable(const string &varname, string &result) const;
 
 
-  void tokenize_params(const string &str, vector<string> &tokens,
-		       bool expand) const;
-
   string r_expand_string(const string &str, ExpandedVariable *expanded) const;
   string r_expand_string(const string &str, ExpandedVariable *expanded) const;
   string r_scan_variable(const string &str, size_t &vp) const;
   string r_scan_variable(const string &str, size_t &vp) const;
   string r_expand_variable(const string &str, size_t &vp,
   string r_expand_variable(const string &str, size_t &vp,
@@ -73,9 +81,11 @@ private:
 
 
   string expand_wildcard(const string &params) const;
   string expand_wildcard(const string &params) const;
   string expand_isdir(const string &params) const;
   string expand_isdir(const string &params) const;
+  string expand_isfile(const string &params) const;
   string expand_libtest(const string &params) const;
   string expand_libtest(const string &params) const;
   string expand_bintest(const string &params) const;
   string expand_bintest(const string &params) const;
   string expand_shell(const string &params) const;
   string expand_shell(const string &params) const;
+  string expand_standardize(const string &params) const;
   string expand_firstword(const string &params) const;
   string expand_firstword(const string &params) const;
   string expand_patsubst(const string &params) const;
   string expand_patsubst(const string &params) const;
   string expand_filter(const string &params) const;
   string expand_filter(const string &params) const;
@@ -91,14 +101,16 @@ private:
   string expand_and(const string &params) const;
   string expand_and(const string &params) const;
   string expand_upcase(const string &params) const;
   string expand_upcase(const string &params) const;
   string expand_downcase(const string &params) const;
   string expand_downcase(const string &params) const;
+  string expand_cdefine(const string &params) const;
   string expand_closure(const string &params) const;
   string expand_closure(const string &params) const;
+  string expand_unmapped(const string &params) const;
+  string expand_function(const string &funcname, const PPSubroutine *sub,
+			 const string &params) const;
   string expand_map_variable(const string &varname, const string &params) const;
   string expand_map_variable(const string &varname, const string &params) const;
   string expand_map_variable(const string &varname, const string &expression,
   string expand_map_variable(const string &varname, const string &expression,
 			     const vector<string> &keys) const;
 			     const vector<string> &keys) const;
 
 
-  const MapVariableDefinition &
-  find_map_variable(const string &varname) const;
-  const MapVariableDefinition &
+  MapVariableDefinition &
   p_find_map_variable(const string &varname) const;
   p_find_map_variable(const string &varname) const;
 
 
   void glob_string(const string &str, vector<string> &results) const;
   void glob_string(const string &str, vector<string> &results) const;
@@ -112,7 +124,6 @@ private:
 
 
   typedef map<string, MapVariableDefinition> MapVariables;
   typedef map<string, MapVariableDefinition> MapVariables;
   MapVariables _map_variables;
   MapVariables _map_variables;
-  static MapVariableDefinition _null_map_def;
 
 
   PPScope *_parent_scope;
   PPScope *_parent_scope;
   typedef vector<PPScope *> ScopeStack;
   typedef vector<PPScope *> ScopeStack;

+ 46 - 0
ppremake/ppSubroutine.cxx

@@ -6,6 +6,7 @@
 #include "ppSubroutine.h"
 #include "ppSubroutine.h"
 
 
 PPSubroutine::Subroutines PPSubroutine::_subroutines;
 PPSubroutine::Subroutines PPSubroutine::_subroutines;
+PPSubroutine::Subroutines PPSubroutine::_functions;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PPSubroutine::define_sub
 //     Function: PPSubroutine::define_sub
@@ -46,3 +47,48 @@ get_sub(const string &name) {
     return (*si).second;
     return (*si).second;
   }
   }
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPSubroutine::define_func
+//       Access: Public, Static
+//  Description: Adds a function to the global list with the
+//               indicated name.  This is similar to a subroutine
+//               except it is to be invoked via a variable reference,
+//               instead of by a #call function.  It cannot be
+//               shadowed by a local variable; it will always override
+//               any variable definition.
+//
+//               The subroutine pointer must have been recently
+//               allocated, and ownership of the pointer will be
+//               passed to the global list; it may later delete it if
+//               another subroutine is defined with the same name.
+////////////////////////////////////////////////////////////////////
+void PPSubroutine::
+define_func(const string &name, PPSubroutine *sub) {
+  Subroutines::iterator si;
+  si = _functions.find(name);
+  if (si == _functions.end()) {
+    _functions.insert(Subroutines::value_type(name, sub));
+  } else {
+    delete (*si).second;
+    (*si).second = sub;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPSubroutine::get_func
+//       Access: Public, Static
+//  Description: Returns the previously-defined function with the
+//               given name, or NULL if there is no such function
+//               with that name.
+////////////////////////////////////////////////////////////////////
+const PPSubroutine *PPSubroutine::
+get_func(const string &name) {
+  Subroutines::const_iterator si;
+  si = _functions.find(name);
+  if (si == _functions.end()) {
+    return NULL;
+  } else {
+    return (*si).second;
+  }
+}

+ 5 - 0
ppremake/ppSubroutine.h

@@ -20,14 +20,19 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class PPSubroutine {
 class PPSubroutine {
 public:
 public:
+  vector<string> _formals;
   vector<string> _lines;
   vector<string> _lines;
 
 
 public:
 public:
   static void define_sub(const string &name, PPSubroutine *sub);
   static void define_sub(const string &name, PPSubroutine *sub);
   static const PPSubroutine *get_sub(const string &name);
   static const PPSubroutine *get_sub(const string &name);
 
 
+  static void define_func(const string &name, PPSubroutine *sub);
+  static const PPSubroutine *get_func(const string &name);
+
   typedef map<string, PPSubroutine *> Subroutines;
   typedef map<string, PPSubroutine *> Subroutines;
   static Subroutines _subroutines;
   static Subroutines _subroutines;
+  static Subroutines _functions;
 };
 };
 
 
 #endif
 #endif

+ 2 - 0
ppremake/ppremake.h

@@ -14,9 +14,11 @@
 #ifdef HAVE_IOSTREAM
 #ifdef HAVE_IOSTREAM
 #include <iostream>
 #include <iostream>
 #include <fstream>
 #include <fstream>
+#include <strstream>
 #else
 #else
 #include <iostream.h>
 #include <iostream.h>
 #include <fstream.h>
 #include <fstream.h>
+#include <strstream.h>
 #endif
 #endif
 
 
 #include <string>
 #include <string>