--
-- features.lua
--
-- $Id$
--
-- Lua 5.2 module providing a mingw-get setup hook for configuration of
-- the user's MinGW GCC compiler <features.h> preferences.
--
-- Written by Keith Marshall <keithmarshall@users.sourceforge.net>
-- Copyright (C) 2019, MinGW.org Project
--
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the "Software"),
-- to deal in the Software without restriction, including without limitation
-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
-- and/or sell copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included
-- in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
-- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.
--
--
-- We begin by initializing a container, for construction of a Lua module
-- to encapsulate the content of this source file.
--
   local M = {}
--
-- mingw-get passes the MinGW installation root directory path, in the
-- $MINGW32_SYSROOT environment variable; from this, we deduce the path
-- name for the working copy of the features.h file...
--
   local function syspath( varname )
--
--   ...using this local helper function to ensure that the path name
--   string, returned from the environment, is free from insignificant
--   trailing directory name separators, and that all internal sequences
--   of directory name separators are normalized to a single '/'.
--
     local pathname = os.getenv( varname )
     if pathname
     then
       pathname = string.gsub( pathname, "[/\\]+", "/" )
       pathname = string.match( pathname, "(.*[^/])/*$" )
     end
     return pathname
   end
   local sysroot = syspath( "MINGW32_SYSROOT" )
   if not sysroot
   then
     error( "environment variable MINGW32_SYSROOT may not be set", 0 )
   end
   local config_file_name = sysroot .. "/include/features.h"
--
-- Define a template, whence the default features configuration may be
-- deduced when writing the initial content of the <features.h> file.
--
   local config_default =
   { '/*',
     ' * features.h',
     ' *',
     ' * Features configuration for MinGW.org GCC implementation; users may',
     ' * customize this file, to establish their preferred default behaviour.',
     ' * Projects may provide an alternative, package-specific configuration,',
     ' * either by placing their own customized <features.h> in the package',
     ' * -I path, ahead of the system default, or by assignment of their',
     ' * preferred alternative to the _MINGW_FEATURES_HEADER macro.',
     ' *',
     ' *',
     ' * $'..'Id$',
     ' *',
     ' * Template written by Keith Marshall <keith@users.osdn.me>',
     ' * Copyright (C) 2019, MinGW.org Project.',
     ' *',
     ' *',
     ' * Permission is hereby granted, free of charge, to any person obtaining a',
     ' * copy of this software and associated documentation files (the "Software"),',
     ' * to deal in the Software without restriction, including without limitation',
     ' * the rights to use, copy, modify, merge, publish, distribute, sublicense,',
     ' * and/or sell copies of the Software, and to permit persons to whom the',
     ' * Software is furnished to do so, subject to the following conditions:',
     ' *',
     ' * The above copyright notice, this permission notice, and the following',
     ' * disclaimer shall be included in all copies or substantial portions of',
     ' * the Software.',
     ' *',
     ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS',
     ' * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,',
     ' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL',
     ' * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER',
     ' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING',
     ' * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER',
     ' * DEALINGS IN THE SOFTWARE.',
     ' *',
     ' */',
     '#ifndef __MINGW_FEATURES__',
     '#pragma GCC system_header',
     '',
     '/* Users are expected to customize this header, but it remains subject to',
     ' * automatic updates by system software.  To ensure that any customisation',
     ' * is not ovewritten, during such updates, it MUST observe the following:',
     ' *',
     ' * This header MUST define __MINGW_FEATURES__; the definition MUST begin',
     ' * with "#define __MINGW_FEATURES__ (__MINGW_FEATURES_BEGIN__) \\"; it MUST',
     ' * extend over multiple lines, and terminate with "__MINGW_FEATURES_END__";',
     ' * intervening lines may enumerate any defined features, one per line, and',
     ' * each specified as an argument to either the __MINGW_FEATURE_ENABLE__(),',
     ' * or the __MINGW_FEATURE_IGNORE__() macro, (ensuring that at least one',
     ' * space separates either of these macro names from its parenthesized',
     ' * argument name).',
     ' *',
     ' * CAUTION:',
     ' * If customizing this features configuration, ALWAYS refer to features',
     ' * using their designated symbolic constant names; NEVER usurp the use of',
     ' * these symbolic constants for any other purpose, and NEVER assume that',
     ' * any such constant has a specific value ... their definitions may vary',
     ' * between distinct MinGW Runtime Library software releases!',
     ' */',
     '#define __MINGW_FEATURES__    (__MINGW_FEATURES_BEGIN__)        \\',
     ' __MINGW_FEATURE_IGNORE__     (__MINGW_ANSI_STDIO__)            \\',
     ' __MINGW_FEATURE_IGNORE__     (__MINGW_LC_MESSAGES__)           \\',
     ' __MINGW_FEATURE_IGNORE__     (__MINGW_LC_ENVVARS__)            \\',
     ' __MINGW_FEATURES_END__',
     '',
     '#endif	/* !__MINGW_FEATURES__: $'..'RCSfile$: end of file */'
   }
--
   local function defines( name )
--
--   A local helper function, to generate a string.match
--   pattern for identification of any C #define statement,
--   which defines a specified symbol "name"
--
     return "^%s*#%s*define%s+" .. name
   end
--
   local function feature( value )
--
--   A local helper function, to generate a string.match
--   pattern for identification of a specified parenthesized
--   value field, within a C #define statement.
--
     return "%s+%(%s*" .. value .. "%s*%)%s*"
   end
--
   local function begins_definition( line, name, value )
--
--   A local helper function to check whether any input "line"
--   represents the first of a multiline C #define for the "name"
--   symbol, with its initial field matching the parenthesized
--   "value" token (default: "__MINGW_FEATURES_BEGIN__").
--
     if not value then value = "__MINGW_FEATURES_BEGIN__" end
     return string.match( line, defines( name ) .. feature( value ) .. "\\$" )
   end
--
-- In the event that a features configuration has already been
-- specified for this installation, capture this into internal
-- "as built" configuration tables...
--
   local current_config, current_features
   local config = io.open( config_file_name )
   if config
   then
--   ...always starting collection into table "current_config"...
--
     current_config = {}
     local active_list = current_config
--
--   ...reading the existing configuration file, line by line...
--
     for line in config:lines()
     do if active_list == current_features
        and string.match( line, "^%s*__MINGW_FEATURES_END__%s*$" )
        then
--	  ...noting that actual configuration options will have
--	  been diverted to the "current_features" table; when all
--	  such options have been captured, redirect any residual
--	  content to the "current_config" table.
--
	  active_list = current_config
	end
--
--	Capture the current configuration record, into whichever
--	diversion is currently active.
--
	table.insert( active_list, line )
--
	if begins_definition( line, "__MINGW_FEATURES__" )
	then
--	  When we find the first line of a (possibly) well-formed
--	  features configuration, divert capture of its subsequent
--	  lines to a new "current_features" table.
--
	  current_features = {}
	  active_list = current_features
	end
     end
--
--   When capture is complete, ensure that the input stream file
--   is closed; (we may want to reopen it later, to rewrite it).
--
     io.close( config )
--
--   Before we go any further, check for well-formedness of the
--   configuration which we have just captured; effectively...
--
     if not current_features
     or active_list == current_features
     then
--     ...when no "current_features" diversion has been created,
--     or such a diversion remains open for capture, then an error
--     has occurred, and the configuration is not well-formed.
--
       local function config_error( reason )
	 error( config_file_name .." ".. reason, 0 )
       end
       for ref, line in next, current_config
       do if string.match( line, defines( "__MINGW_FEATURES__" ) )
	  then
--	    In this case, a "__MINGW_FEATURES__" definition is
--	    present, but it is not well-formed; (note that, here,
--	    we rescan the "current_config" table, using a less
--	    rigorous criterion for detection of a definition of
--	    of "__MINGW_FEATURES__" than that which is required
--	    to open the "current_features" diversion)...
--
	    config_error( "has malformed __MINGW_FEATURES__ definition" )
	  end
       end
--     ...while in this case, no "__MINGW_FEATURES__" definition
--     was found, (well-formed, or otherwise).
--
       config_error( "does not define __MINGW_FEATURES__" )
     end
   end
--
--
   local function update_configuration( stream_file, template, current )
--
--   A function to write a features configuration to a designated output
--   stream, based on a specified template, reproducing and encapsulating
--   any existing configuration which may also have been specified...
--
     local function stream_file_writeln( line )
--
--     ...using this helper function to write each line.
--
       stream_file:write( line .. "\n" )
     end
--
     if current
     then
--     An existing configuration header is to be updated.  An image of
--     its original content has already been loaded into "current"; copy
--     it back to the original file, line by line...
--
       for ref, line in next, current
       do if begins_definition( line, "__MINGW_FEATURES__" )
	  then
--	    ...until we reach the first active configuration record.
--	    We now wish to merge any new configuration options from the
--	    template, into the existing configuration; work through the
--	    template, line by line, ignoring all lines...
--
	    local merge = false
	    for ref, sub in next, template
	    do if begins_definition( sub, "__MINGW_FEATURES__" )
	       then
--		 ...until we find the first line of a (possibly)
--		 well-formed features configuration record; when we
--		 find this, we decompose and reassemble it, so that
--		 we may preserve a tabulation format matching the
--		 longer of the template and actual configuration
--		 records.
--
		 local def = string.match( sub, "^[^(]*" )
		 local usr = string.match( line, "^[^(]*" )
		 if string.len( usr ) > string.len( def )
		 then
		   def = usr
		 end
		 usr = string.match( line, "%(.*" )
		 sub = string.match( sub, "%(.*" )
		 if string.len( usr ) > string.len( sub )
		 then
		   sub = usr
		 end
--
--		 Write out the reassembled features configuration
--		 start record, and activate the features merge.
--
		 stream_file_writeln( def .. sub )
		 merge = true
--
--	       During the merge operation, when we find the record
--	       terminator within the template...
--
	       elseif string.match( sub, "^%s*__MINGW_FEATURES_END__%s*$" )
	       then
--		 ...we immediately write out any additional option
--		 specifications, which remain in the user specified
--		 configuration...
--
		 for ref, sub in next, current_features
		 do
		   stream_file_writeln( sub )
		 end
--
--		 ...and break out of the merge cycle.
--
		 break
--
	       elseif merge
	       then
--	         While we remain within the merge cycle, we extract
--	         the identification of each configuration option from
--	         the template, comparing it with all entries in the
--	         "current_features" table...
--
		 tag = string.match( sub, "%(.*%)" )
		 for ref, usr in next, current_features
		 do if string.match( usr, tag )
		    then
--
--		      ...and, when we find a match, we favour the
--		      existing configuration record over that from
--		      the template...
--
		      sub = usr
--
--		      ...remove the corresponding entry from the
--		      "current_features" table, and break out of
--		      the inner matching loop, before...
--
		      table.remove( current_features, ref )
		      break
		    end
		 end
--
--		 ...in either event, writing out a copy of each
--		 selected configuration record.
--
		 stream_file_writeln( sub )
	       end
	    end
	  else
--
--	    For every line in the existing configuration file, outside
--	    the scope of the "__MINGW_FEATURES__" definition itself, we
--	    simply write out a verbatim copy of each line.
--
	    stream_file_writeln( line )
	  end
       end
     else
--
--     There is no existing configuration header, so we simply reproduce
--     the template, processing it line by line...
--
       for ref, line in next, template
       do
--	 ...and writing out each line individually.
--
	 stream_file_writeln( line )
       end
     end
   end
--
--
   function M.pathname( suffix )
--
--   An exported utility function, to facilitate identification of
--   the full MS-Windows path name for the features.h configuration
--   file, as appropriate to the current installation...
--
     if suffix
     then
--     ...appending any suffix which may have been specified, (e.g.
--     to specify a reference to the features.h.sample file)...
--
       return config_file_name .. suffix
     end
--
--   ...otherwise, specifying a reference to features.h itself.
--
     return config_file_name
   end
--
--
   function M.initialize( stream_file )
--
--   Primary initialization function; overwrites any existing features
--   configuration header, opened for writing as "stream_file"...
--
     if not stream_file
     then
--     ...or falling back to "io.stderr", in the event that no output
--     stream file has been opened...
--
       stream_file = io.stdout
     end
--
--   ...replacing any existing configuration with an exact copy of the
--   "config_default" content specified within this module.
--
     update_configuration( stream_file, config_default )
   end
--
--
   function M.update( stream_file )
--
--   Primary API function, exported for use by mingw-get (or any other
--   client); overwrites any existing configuration header file, which
--   has been opened for writing as "stream_file"...
--
     if not stream_file
     then
--     ...or falling back to "io.stderr", in the event that no output
--     stream file has been opened...
--
       stream_file = io.stdout
     end
--
--   ...preserving any existing configuration, with the addition of
--   any configuration options which have been included in the default
--   template, but which do not yet appear in the existing header.
--
     update_configuration( stream_file, config_default, current_config )
   end
--
-- Since this source file is intended to be loaded as a Lua module, we
-- must ultimately return a reference handle for it.
--
   return M
--
-- $RCSfile$: end of file */
