/* ** 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*/ }