/*
** 	BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
** 	Written by Dave Nanian and Michael Strickman.
*/

/*
**		language.cb:
**
**		This file contains the support modules for BRIEF's language package
**	system.
*/

void _call_packages (string event_name);
void _parse_packages (string event_name);
string process_package (string event_name, string mac_name, string existing_packages, string file_ext);
void _reset_packages ();
string _package_abbrev ();
string escape_re (string original, ~string);

extern int	_package_buf;	//	Parsed language package information.

string	_curr_active;		//	The list of currently active packages.

void _init ()
{
	int	old_buf;

	register_macro (1, "_call_on_packages");
	register_macro (6, "_call_first_packages");

	old_buf = set_buffer (_package_buf = create_buffer ("Packages", NULL, 1));
	delete_line ();
	set_buffer (old_buf);
}

void _call_on_packages ()
{
	_call_packages ("_on");
}

void _call_first_packages ()
{
	string filename;

	inq_names (NULL, NULL, filename);

	if (inq_message () == "New file (unable to open " + filename + ").")
		_call_packages ("_new");
	else
		_call_packages ("_existing");

	_call_packages ("_first");
}

/*
**		_call_packages:
**
**		This macro is the main event dispatcher for the BRIEF language
**	package system.  It calls packages specified in the BPACKAGES
**	environment variable in response to events generated by the system or
**	by the user using some housekeeping information generated by
**	_parse_packages that allows rapid access to them.
*/

void _call_packages (string event_name)
{
	int	old_buf,
			loc;

	string	file_ext;

	inq_names (NULL, file_ext);

	if (_curr_active != "")
		_reset_packages ();

	old_buf = set_buffer (_package_buf);
	top_of_buffer ();

	/*
	**		First, we check to see if we've already encounted this extension
	**	and event.  If so, the information we need is present in the
	**	package buffer, and we call the macros therein.
	**
	**		If either the extension or event haven't been encountered before,
	**	we call _parse_packages to do more work.
	*/

	if (loc = search_fwd ("." + file_ext + event_name + ";", 0))
		{
		string	to_call,
					off_macro;

		next_char (loc - 1);
		to_call = trim (read ());
		set_buffer (old_buf);

		while (strlen (to_call))
			{
			if (!(loc = index (to_call, ",")))
				loc = strlen (to_call) + 1;

			if (event_name != "_on")
				execute_macro (substr (to_call, 1, loc - 1));
			else if ((off_macro = execute_macro (substr (to_call, 1, loc - 1))) != "")
				_curr_active += (strlen (_curr_active) ? ";" : "") + off_macro;

			to_call = substr (to_call, loc + 1);
			}
		}
	else
		{
		set_buffer (old_buf);
		_parse_packages (event_name);
		}
}

/*
**		_reset_packages:
**
**		This macro processes the "off" event.	Any macros that returned
**	"off" event macros from their event handles have those strings kept
**	in the _curr_active global variable.  These macros are called here.
**
**		Note that "off" macro strings should never contain semicolons.
*/

void _reset_packages ()
{
	int	loc;

	while (strlen (_curr_active))
		{
		if (!(loc = index (_curr_active, ";")))
			loc = strlen (_curr_active) + 1;

		execute_macro (substr (_curr_active, 1, loc - 1));
		_curr_active = substr (_curr_active, loc + 1);
		}
}


/*
**		_evaluate_package:
**
**		This macro takes a macro name and an extension and constructs a
**	package name from it, using all of the possible equivalent extensions.
*/

string _evaluate_package (string extension, string macro_name)
{
	int	old_buf = set_buffer (_package_buf),
			len;

	string	package_name;

	top_of_buffer ();

	if (len = search_fwd ("." + extension + macro_name + ";", 0))
		{
		next_char (len - 1);
		package_name = trim (read ());
		}
	else
		{
		int	loc;

		string	equivalents;

		next_char (search_fwd ("." + escape_re (extension) + "_equivalents") - 1);
		equivalents = extension + trim (read ()) + "==";

		while (loc = index (equivalents, "="))
			if (inq_macro (package_name = (loc != 1 ? "." : "") + substr (equivalents, 1, loc - 1) + macro_name))
				break;
			else
				equivalents = substr (equivalents, loc + 1);

		beginning_of_line ();
		insert ("." + extension + macro_name + ";" + package_name + "\n");
		}
	set_buffer (old_buf);
	returns (package_name);
}

/*
**		_parse_packages:
**
**		This macro deals with more extensive package expansion the first time
**	a file is read, and the first time an event is executed.
*/

void _parse_packages (string event_name)
{
	string	bpackages,
				to_call,
				mac_name,
				file_ext,
				escaped_ext,
				equivalents,
				pattern;

	int	loc,
			pass,
			file_buf;

	/*
	**		First, we get the event type and the extension of the file.
	*/

	inq_names (NULL, file_ext);
	escaped_ext = escape_re (file_ext);

	/*
	**		The global _package_buf contains housekeeping information that
	**	allows the language system to keep parsed event information around
	**	for the duration of the editing session.	Each event occupies one
	**	line in the buffer (per extension), and one additional line is
	**	taken per extension to save the pre-parsed packages and arguments.
	**
	**		Extensions are always found together in the package buffer; the
	**	events come first, followed by the pre-parsed information.	This
	**	allows the search pattern to fall through to the pre-parsed
	**	information if the event hasn't occurred yet.
	**
	**		The format of the strings is:
	**
	**		.[extension][event name, or nothing if pre_parse];[information]
	*/

	file_buf = set_buffer (_package_buf);
	top_of_buffer ();

	/*
	**		This search locates any string in the buffer that corresponds
	** to the current extension.	If the event is in the buffer,
	**	_call_packages will have already dealt with it.  If not, we try
	**	to locate the pre_parse information here.
	*/

	if (loc = search_fwd ("." + file_ext + ";", 0))
		{
		next_char (loc - 1);
		to_call = trim (read ());
		file_ext = "." + file_ext;
		beginning_of_line ();
		}
	else
		{
		/*
		**		If no pre-parse information is present in the buffer for
		**	the given extension, the BPACKAGES string must be retrieved
		**	from the environment and parsed.  Note that this will only
		**	happen once per extension.
		**
		**		Since the BPACKAGES string is quite complex, and a given
		**	extension can appear more than once (and with a number of
		**	different packages), the parser loops and searches for the
		**	extension until it can't be located any more.
		**
		**		If the extension isn't found at all, the "default"
		**	extension is used and a second pass is made over the
		**	BPACKAGES string.
		**
		**		If neither the extension nor the "default" extension is
		**	found, no events are generated.
		*/

		int	located;

		pattern = "<|[,;] @\\c" + escaped_ext + " @{[,\\-][~\\-,;:]@}@:[~:\n;]+[\n;]";
		bpackages = trim (ltrim (compress (inq_environment ("BPACKAGES")))) + "\n";

		while (pass != 2 && !located)
			{
			while (loc = search_string (pattern, bpackages, NULL, NULL, 0))
				{
				string	curr_ext,
							this_package_list;

				++located;

				bpackages = substr (bpackages, loc);

				if (loc = index (bpackages, ";"))
					{
					this_package_list = substr (bpackages, 1, loc - 1);
					bpackages = substr (bpackages, loc + 1);
					}
				else
					{
					this_package_list = trim (bpackages);
					bpackages = "";
					}
				/*
				**		Now we have the "raw" BPACKAGES information.
				**	At this point, we need to check for extension
				**	substitution.
				*/

				if ((loc = index (this_package_list, "-")) && loc < index (this_package_list, ":"))
					{
					this_package_list = substr (this_package_list, loc + 1);

					if (!((loc = index (this_package_list, ",")) && loc < index (this_package_list, ":")))
						loc = index (this_package_list, ":");

					curr_ext = trim (ltrim (substr (this_package_list, 1, loc - 1)));
					equivalents += "=" + curr_ext;
					}
				else
					curr_ext = file_ext;

				/*
				**		All of the pre-expansion packages for the
				**	current extension are combined together in the
				**	to_call string separated by commas.  This
				**	string is used later to expand and call the
				**	individual package macros, and is inserted
				**	into the buffer as pre-parse information
				**	once the initial parse has been completed.
				*/

				to_call += (strlen (to_call) ? ",=" : "=") + curr_ext + ",," + substr (this_package_list, index (this_package_list, ":") + 1);
				}
			/*
			**		If we can't find the extension any more and we've
			**	located some packages to call, the parse is over.
			**	Otherwise, we use "default" as the extension and try
			**	one more time.
			*/

			pattern = "<|[,;] @\\cdefault @{[,\\-][~\\-,;:]@}@:[~:\n;]+[\n;]";
			++pass;
			}
		file_ext = "." + file_ext;

		/*
		**		If the package buffer didn't contain any information about
		**	the current file extension, we insert the pre-parse information
		**	at the top of the buffer.	This information can be used later
		**	to avoid doing an additional BPACKAGES parse.
		*/

		beginning_of_line ();

		/*
		**		The extra comma here is used to generate a "null"
		**	event, which, for the first event, looks something like:
		**
		**		_first or .c_first
		**
		**		These are generated to allow packages to chain into
		**	various events without requiring them to be listed in
		**	the BPACKAGES environment variable.  Note that each
		**	given extension in the parsed BPACKAGES string also checks
		**	for generic event macros.  This ensures that all of the macros
		**	for a given extension or set of extensions are called.
		*/

		to_call = "=" + substr (file_ext, 2) + ",," + to_call;
		insert (file_ext + ";" + to_call + "\n");

		/*
		**		We move up here because the event information must
		**	appear before the pre-parse information, and the "\n"
		**	that was inserted moves us down a line.
		*/

		up ();

		/*
		**		We also insert the list of equivalent extensions at
		**	this point in the buffer.  This list can be used by other
		**	extension-type macros that want to know about equivalence.
		*/

		insert (file_ext + "_equivalents" + equivalents + "\n");
		}
	set_buffer (file_buf);
	bpackages = "";

	/*
	**		At this point, we've got a list of pre-expanded package
	**	names that look something like "s,pvcs,wp".	These have to
	**	be expanded and converted into the event and extension
	**	specific variety.  Each individual package is separated and
	**	passed to process_package, which expands the name (if
	**	necessary), handles extension-specific and generic naming
	**	convensions, deals with parameters, and the like.	Once
	**	completed, the expanded package name (if any) is returned
	**	and saved in another string that will be inserted as the
	**	event-specific information in the package buffer.
	*/

	while (strlen (to_call))
		{
		if (!(loc = index (to_call, ",")))
			loc = strlen (to_call) + 1;

		if (substr (to_call, 1, 1) == "=")
			escaped_ext = substr (to_call, 2, loc - 2);
		else
			{
			mac_name = process_package (event_name, ltrim (trim (substr (to_call, 1, loc - 1))), bpackages, escaped_ext);

			if (strlen (mac_name))
				bpackages += (strlen (bpackages) ? "," : "") + mac_name;
			}
		to_call = substr (to_call, loc + 1);
		}
	/*
	**		Since the event-specific information wasn't found in the buffer,
	**	it's inserted here.
	*/

	file_buf = inq_buffer ();
	set_buffer (_package_buf);
	insert (file_ext + event_name + ";" + bpackages + "\n");
	set_buffer (file_buf);
}

/*
**		process_package:
**
**		This macro converts generic package names into their generic or
**	extension-specific macro name counterparts.
*/

string process_package (string event_name, string mac_name, string existing_packages, string file_ext)
{
	string	real_ext,
				initial_mac,
				parms;

	int	loc,
			processed,
			length;

	existing_packages = "," + existing_packages + ",";

	inq_names (NULL, real_ext);

	file_ext = "." + file_ext;
	real_ext = "." + real_ext;

	if (loc = index (mac_name, " "))
		{
		parms = substr (mac_name, loc + 1);
		mac_name = substr (mac_name, 1, loc - 1);
		}

	/*
	**		If part of the package name is found in the
	**	abbreviation list, the full name is retrieved and used
	**	instead.  Note that the macros in the abbreviation list
	**	could be demotable groups (separated by commas).
	*/

	initial_mac = _package_abbrev ();

	if (strlen (mac_name) && (loc = search_string ("[,;]" + mac_name + "*;", initial_mac, length)))
		initial_mac = substr (initial_mac, loc + 1, length - 2);
	else
		initial_mac = mac_name;

	/*
	**		This loop searches for specific and generic event
	**	handler macros.  First, specific macros are searched
	**	for.	If none are found, the last member of the
	**	demotable group is searched for generically.  This is
	**	because generic higher level demotion macros don't
	**	make any sense (they'd override anything else, and
	**	wouldn't leave anything to demote to).
	*/

	while (!processed)
		{
		if (!(loc = index (initial_mac, ",")))
			loc = strlen (initial_mac) + 1;

		mac_name = substr (initial_mac, 1, loc - 1);
		initial_mac = substr (initial_mac, loc + 1);

		mac_name = (strlen (mac_name) ? "_" : "") + mac_name + event_name;

		if (processed = inq_macro (real_ext + mac_name))
			mac_name = real_ext + mac_name;
		else if (real_ext != file_ext && (processed = inq_macro (file_ext + mac_name)))
			mac_name = file_ext + mac_name;
		else if (!strlen (initial_mac))
			{
			processed = inq_macro (mac_name);
			break;
			}
		}
	/*
	**		Here we check to see if the macro has been called
	**	before.	If so, we ignore it.
	*/

	if (processed && index (existing_packages, "," + mac_name + ","))
		processed = 0;

	/*
	**		If the macro has been processed & located, we call it with its
	**	parameters.
	*/

	if (processed)
		{
		string	generic_macro_called;

		if (strlen (parms))
			mac_name += " " + parms;

		/*
		**		If current event is an "on" event, we retain the return
		**	value (if non-null) as an "off" event macro name.
		**
		**		Note also that we ensure that a generically named generic
		**	event macro is always called before the specifically named one,
		**	if any.
		*/

		if (initial_mac == "" && event_name != mac_name && !index (existing_packages, "," + event_name + ",") && inq_macro (event_name))
			{
			generic_macro_called = event_name + ",";

			if ((initial_mac = execute_macro (event_name)) != "" && event_name == "_on")
				_curr_active += (strlen (_curr_active) ? ";" : "") + initial_mac;
			}
		if ((initial_mac = execute_macro (mac_name)) != "" && event_name == "_on")
			_curr_active += (strlen (_curr_active) ? ";" : "") + initial_mac;

		returns (generic_macro_called + mac_name);
		}
	else
		returns ("");
}

/*
**		_package_abbrev:
**
**		This macro is at the terminus of the chain.	It returns a string
**	consisting of the abbreviations and demotable groups for the built-in
**	BRIEF packages (indenting, word processing, pvcs and tlib).
*/

string _package_abbrev ()
{
	returns (";template,smart,regular;pvcs;wp;tlib;vc;");			/*vc*/
}
