9fb6c780b7d0c42e55d97e7daed1e260e563e248
[feisty_meow.git] / nucleus / tools / clam_tools / value_tagger.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : value_tagger                                                      *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *  Purpose:                                                                   *
7 *                                                                             *
8 *    Scoots through the entire known code base and builds a list of all the   *
9 *  outcome (and filter) values for that tree.  A manifest of the names is     *
10 *  produced.  Most of the behavior is driven by the ini file whose name is    *
11 *  passed on the command line.                                                *
12 *    Note that the set of items that can be searched for can be specified     *
13 *  in the ini file, although they must follow the format of:                  *
14 *      pattern(name, value, description)                                      *
15 *  where the "pattern" is the search term and the other three items specify   *
16 *  the enumerated value to be marked.                                         *
17 *                                                                             *
18 *******************************************************************************
19 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
20 * redistribute it and/or modify it under the terms of the GNU General Public  *
21 * License as published by the Free Software Foundation; either version 2 of   *
22 * the License or (at your option) any later version.  This is online at:      *
23 *     http://www.fsf.org/copyleft/gpl.html                                    *
24 * Please send any updates to: fred@gruntose.com                               *
25 \*****************************************************************************/
26
27 #include <application/application_shell.h>
28 #include <application/command_line.h>
29 #include <application/hoople_main.h>
30 #include <application/windoze_helper.h>
31 #include <basis/environment.h>
32 #include <basis/functions.h>
33 #include <basis/utf_conversion.h>
34 #include <configuration/ini_configurator.h>
35 #include <filesystem/byte_filer.h>
36 #include <filesystem/directory_tree.h>
37 #include <filesystem/filename.h>
38 #include <loggers/combo_logger.h>
39 #include <loggers/critical_events.h>
40 #include <loggers/program_wide_logger.h>
41 #include <structures/set.h>
42 #include <structures/string_array.h>
43 #include <structures/string_table.h>
44 #include <timely/time_stamp.h>
45 #include <textual/parser_bits.h>
46
47 #include <sys/stat.h>
48
49 #include "../../library/algorithms/sorts.h"
50 #ifdef __WIN32__
51   #include <io.h>
52 #endif
53
54 #undef LOG
55 #define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), astring(s))
56
57 using namespace algorithms;
58 using namespace application;
59 using namespace basis;
60 using namespace configuration;
61 using namespace filesystem;
62 using namespace loggers;
63 using namespace structures;
64 using namespace textual;
65 using namespace timely;
66
67 const int LONGEST_SEPARATION = 128;
68   // the longest we expect a single line of text to be in definition blocks.
69   // if the definition of an outcome or whatever is farther away than this
70   // many characters from a comment start, we will no longer consider the
71   // line to be commented out.  this pretty much will never happen unless it's
72   // intentionally done to break this case.
73
74 const char *SKIP_VALUE_PHRASE = "SKIP_TO_VALUE";
75   // the special phrase we use to indicate that values should jump to
76   // a specific number.
77
78 ////////////////////////////////////////////////////////////////////////////
79
80 // this object records all the data that we gather for the defined items.
81 class item_record
82 {
83 public:
84   astring _name;
85   int _value;
86   astring _description;
87   astring _path;
88   astring _extra_tag;  //!< records special info for links.
89
90   item_record(const astring &name = astring::empty_string(), int value = 999,
91       const astring &description = astring::empty_string(),
92       const astring &path = astring::empty_string(),
93       const astring &extra_tag = astring::empty_string())
94   : _name(name), _value(value), _description(description), _path(path),
95     _extra_tag(extra_tag) {}
96 };
97
98 ////////////////////////////////////////////////////////////////////////////
99
100 class search_record
101 {
102 public:
103   search_record(const astring &search = astring::empty_string(),
104       bool is_link = false, search_record *link = NULL_POINTER)
105   : _search(search), _no_modify(false), _is_link(is_link), _our_link(link),
106     _current_value(0), _value_increment(1) {}
107
108   // these properties are available for both real or linked records.
109   astring _search;  // our term to search for in the files.
110   bool _no_modify;  // true if values should not be automatically incremented.
111   astring _tag;  // extra information attached to this type.
112   
113   bool is_link() const { return _is_link; }
114     // returns true if this object is leeching off another object for data.
115
116   search_record *our_link() const { return _our_link; }
117     // returns the object that this object is a mere shadow of.
118
119   symbol_table<item_record> &definitions() {
120     if (is_link()) return _our_link->_definitions;
121     else return _definitions;
122   }
123   
124   int &current_value() {
125     if (is_link()) return _our_link->_current_value;
126     else return _current_value;
127   }
128
129   int &value_increment() {
130     if (is_link()) return _our_link->_value_increment;
131     else return _value_increment;
132   }
133
134   int_set &out_of_band() {
135     if (is_link()) return _our_link->_out_of_band;
136     else return _out_of_band;
137   }
138
139 private:
140   bool _is_link;  // true if this object links to another.
141   search_record *_our_link;  // the search we share for our values.
142   symbol_table<item_record> _definitions;
143     // the definitions that we found in the code.
144   int _current_value;  // the next value to use for our term.
145   int _value_increment;
146     // how much to add for each new value, if this is an incrementing search.
147   int_set _out_of_band;
148     // values we've seen that were premature.  we always want to honor this
149     // set, if it exists, but there will be nothing in it if the search has
150     // completely standard non-incrementing type.  this could be varied by
151     // a non-incrementer linking to a standard incrementer.
152 };
153
154 //! a table of terms that we will search for in the code.
155 class active_searches : public symbol_table<search_record>
156 {};
157
158 ////////////////////////////////////////////////////////////////////////////
159
160 // this class provides us a way to easily sort our items based on value.
161
162 class simple_sorter {
163 public:
164   int _index;
165   int _value;
166   simple_sorter(int index = 0, int value = 0) : _index(index), _value(value) {}
167   bool operator < (const simple_sorter &to_compare) const
168     { return _value < to_compare._value; }
169   bool operator == (const simple_sorter &to_compare) const
170     { return _value == to_compare._value; }
171 };
172
173 class sorting_array : public array<simple_sorter> {};
174
175 ////////////////////////////////////////////////////////////////////////////
176
177 class value_tagger : public application_shell
178 {
179 public:
180   value_tagger();
181   virtual ~value_tagger();
182   DEFINE_CLASS_NAME("value_tagger");
183   int execute();
184   int print_instructions_and_exit();
185
186   bool process_tree(const astring &path);
187     // called on each directory hierarchy that we need to process.
188
189   bool process_file(const astring &path);
190     // examines the file specified to see if it matches our needs.
191
192   bool parse_define(const astring &scanning, int indy, astring &name,
193           int &value, astring &description, int &num_start, int &num_end);
194     // processes the string in "scanning" to find parentheses surrounding
195     // the "name", "value" and "description".  the "description" field may
196     // occupy multiple lines, so all are gathered together to form one
197     // unbroken string.  the "num_start" and "num_end" specify where the
198     // numeric value was found, in case it needs to be patched.
199
200 private:
201   ini_configurator *_ini;  // the configuration for what we'll scan.
202   string_table _dirs;  // the list of directories.
203   string_table _dirs_seen;  // full list of already processed directories.
204   filename _manifest_filename;  // the name of the manifest we'll create.
205   byte_filer _manifest;  // the actual file we're building.
206   active_searches _search_list;  // tracks our progress in scanning files.
207   int_array _search_ordering;
208     // lists the terms in the order they should be applied.  initially this
209     // carries the first pass items, but later will be reset for second pass.
210   int_array _postponed_searches;
211     // lists the searches that must wait until the main search is done.
212   string_table _modified_files;  // the list of files that we touched.
213 };
214
215 ////////////////////////////////////////////////////////////////////////////
216
217 value_tagger::value_tagger()
218 : application_shell(),
219   _ini(NULL_POINTER),
220   _dirs_seen(10)
221 {
222 }
223
224 value_tagger::~value_tagger()
225 {
226   WHACK(_ini);
227 }
228
229 int value_tagger::print_instructions_and_exit()
230 {
231   LOG(a_sprintf("%s usage:", filename(_global_argv[0]).basename().raw().s()));
232   LOG("");
233
234   LOG("\
235 This utility scans a code base for outcome and filter definitions.  It will\n\
236 only scan the header files (*.h) found in the directories specified.  The\n\
237 single parameter is expected to be an INI filename that contains the scanning\n\
238 configuration.  The INI file should be formatted like this (where the $HOME\n\
239 can be any variable substitution from the environment):");
240   LOG("");
241   LOG("\
242 [manifest]\n\
243 output=$HOME/manifest.txt\n\
244 \n\
245 [searches]\n\
246 DEFINE_OUTCOME=1\n\
247 DEFINE_FILTER=1\n\
248 \n\
249 [directories]\n\
250 $HOME/source/lib_src/library/basis\n\
251 $HOME/source/lib_src/library\n\
252 $HOME/source/lib_src/communication/sockets\n\
253 $HOME/source/lib_src/communication\n\
254 $HOME/source/lib_src\n\
255 $HOME/source/app_src\n\
256 $HOME/source/test_src\n\
257 \n\
258 [DEFINE_OUTCOME]\n\
259 first=0\n\
260 increment=-1\n\
261 \n\
262 [DEFINE_FILTER]\n\
263 first=-1\n\
264 increment=1\n\
265 no_modify=1\n\
266 \n\
267 [DEFINE_API_OUTCOME]\n\
268 no_modify=1\n\
269 link=DEFINE_OUTCOME\n\
270 tag=API\n\
271 \n\
272   The \"first\" field defines the starting value that should be assigned to\n\
273 items.\n\
274   The \"increment\" field specifies what to add to a value for the next item.\n\
275   The optional \"no_modify\" flag means that the values should not be auto-\n\
276 incremented; their current value will be used.\n\
277   The optional \"link\" field defines this type of item as using the current\n\
278 values for another type of item.  In this case, API_OUTCOME will use the\n\
279 values for OUTCOME to share its integer space, but API_OUTCOME is not auto-\n\
280 incremented even though OUTCOME is.  This causes the values for OUTCOME and\n\
281 API_OUTCOME to be checked for uniqueness together, but only OUTCOME will be\n\
282 auto-incremented.  Note that only one level of linking is supported currently.\n\
283   The optional \"tag\" can be used to distinguish the entries for a particular\n\
284 search type if needed.  This is most helpful for links, so that they can be\n\
285 distinguished from their base type.\n\
286 \n\
287 ");
288
289   return 23;
290 }
291
292 astring header_string(const astring &build_number)
293 {
294   return a_sprintf("\
295 #ifndef GENERATED_VALUES_MANIFEST\n\
296 #define GENERATED_VALUES_MANIFEST\n\
297 \n\
298 // This file contains all outcomes and filters for this build.\n\
299 \n\
300 // Generated for build %s on %s\n\
301 \n\
302 ", build_number.s(), time_stamp::notarize(true).s());
303 }
304
305 astring footer_string(const byte_array &full_config_file)
306 {
307   return a_sprintf("\n\
308 // End of definitions.\n\
309 \n\
310 \n\
311 // The following is the full configuration for this build:\n\
312 \n\
313 /*\n\
314 \n\
315 %s\n\
316 */\n\
317 \n\
318 \n\
319 #endif // outer guard.\n\
320 ", (char *)full_config_file.observe());
321 }
322
323 int value_tagger::execute()
324 {
325   FUNCDEF("execute");
326   if (_global_argc < 2) {
327     return print_instructions_and_exit();
328   }
329
330   log(time_stamp::notarize(true) + "value_tagger started.", basis::ALWAYS_PRINT);
331
332   astring test_repository = environment::get("FEISTY_MEOW_APEX");
333   if (!test_repository) {
334     astring msg = "\
335 There is a problem with a required build precondition.  The following\r\n\
336 variables must be set before the build is run:\r\n\
337 \r\n\
338   FEISTY_MEOW_APEX    This should point at the root of the build tree.\r\n\
339 \r\n\
340 There are also a few variables only required for CLAM-based compilation:\r\n\
341 \r\n\
342   MAKEFLAGS         This should be set to \"-I $FEISTY_MEOW_APEX/clam\".\r\n\
343 \r\n\
344 Note that on Win32 platforms, these should be set in the System or User\r\n\
345 variables before running a build.\r\n";
346 #ifdef __WIN32__
347     ::MessageBox(0, to_unicode_temp(msg),
348         to_unicode_temp("Missing Precondition"), MB_ICONWARNING|MB_OK);
349 #endif
350     non_continuable_error(class_name(), func, msg);
351   }
352
353   astring ini_file = _global_argv[1];  // the name of our ini file.
354   _ini = new ini_configurator(ini_file, ini_configurator::RETURN_ONLY);
355
356   // read the name of the manifest file to create.
357   _manifest_filename = filename(_ini->load("manifest", "output", ""));
358   if (!_manifest_filename.raw().length()) {
359     non_continuable_error(class_name(), ini_file, "The 'output' file entry is missing");
360   }
361   _manifest_filename = parser_bits::substitute_env_vars(_manifest_filename);
362
363   LOG(astring("Sending Manifest to ") + _manifest_filename);
364   LOG("");
365
366   filename(_manifest_filename).unlink();
367     // clean out the manifest ahead of time.
368
369   // read the list of directories to scan for code.
370   string_table temp_dirs;
371   bool read_dirs = _ini->get_section("directories", temp_dirs);
372   if (!read_dirs || !temp_dirs.symbols()) {
373     non_continuable_error(class_name(), ini_file,
374         "The 'directories' section is missing");
375   }
376   for (int i = 0; i < temp_dirs.symbols(); i++) {
377 //log(astring("curr is ") + current);
378     filename current = filename(parser_bits::substitute_env_vars(temp_dirs.name(i)));
379     _dirs.add(current, "");
380   }
381
382   LOG(astring("Directories to scan..."));
383   LOG(_dirs.text_form());
384
385   astring rdir = environment::get("FEISTY_MEOW_APEX");
386   astring fname;
387   astring parmfile = environment::get("BUILD_PARAMETER_FILE");
388   if (parmfile.t()) fname = parmfile;
389   else fname = rdir + "/build.ini";
390
391   // read the list of search patterns.
392   string_table searches;
393   bool read_searches = _ini->get_section("searches", searches);
394   if (!read_searches || !searches.symbols()) {
395     non_continuable_error(class_name(), ini_file,
396         "The 'searches' section is missing");
397   }
398
399   LOG("Searching for...");
400   LOG(searches.text_form());
401
402   // now make sure that we get the configuration for each type of value.
403   for (int i = 0; i < searches.symbols(); i++) {
404     const astring &curr_name = searches.name(i);
405
406     search_record *check_search = _search_list.find(curr_name);
407     if (check_search) {
408       non_continuable_error(class_name(), ini_file,
409           astring("section ") + curr_name + " is being defined twice");
410     }
411
412     {
413       // check for whether this section is linked to another or not.
414       astring linked = _ini->load(curr_name, "link", "");
415       search_record *our_link_found = NULL_POINTER;
416       if (linked.t()) {
417         // we found that this should be linked to another item.
418         our_link_found = _search_list.find(linked);
419         if (!our_link_found) {
420           non_continuable_error(class_name(), ini_file,
421               astring("linked section ") + curr_name + " is linked to missing "
422                   "section " + linked);
423         }
424         search_record new_guy(curr_name, true, our_link_found);
425         _search_list.add(curr_name, new_guy);
426       } else {
427         // this section is a stand-alone section.
428         search_record new_guy(curr_name);
429         _search_list.add(curr_name, new_guy);
430       }
431     }
432
433     // find our new search cabinet again so we can use it.
434     search_record *curr_search = _search_list.find(curr_name);
435     if (!curr_search) {
436       non_continuable_error(class_name(), ini_file,
437           astring("section ") + curr_name + " is missing from table "
438               "after addition; logic error");
439     }
440
441     // specify some defaults first.
442     int start = 0;
443     int increm = 1;
444     if (!curr_search->is_link()) {
445       // a linked object doesn't get to specify starting value or increment.
446       start = _ini->load(curr_name, "first", start);
447       curr_search->current_value() = start;
448       increm = _ini->load(curr_name, "increment", increm);
449       curr_search->value_increment() = increm;
450     } else {
451       start = curr_search->our_link()->current_value();
452       increm = curr_search->our_link()->value_increment();
453     }
454
455     int no_modify = _ini->load(curr_name, "no_modify", 0);
456     if (no_modify) {
457       curr_search->_no_modify = true;
458     }
459
460     astring tag = _ini->load(curr_name, "tag", "");
461     if (tag.t()) {
462       curr_search->_tag = tag;
463     }
464
465     a_sprintf to_show("%s: no_modify=%s", curr_name.s(),
466          no_modify? "true" : "false");
467
468     if (curr_search->is_link()) {
469       // links show who they're hooked to.
470       to_show += astring(" link=") + curr_search->our_link()->_search;
471     } else {
472       // non-links get to show off their start value and increment.
473       to_show += a_sprintf(" start=%d increment=%d", start, increm);
474     }
475     if (tag.t()) {
476       to_show += astring(" tag=") + curr_search->_tag;
477     }
478     LOG(to_show);
479   }
480   LOG("");
481
482   // now gather some info about the build that we can plug into the manifest.
483
484   byte_filer build_file(fname, "r");
485   if (!build_file.good()) {
486     non_continuable_error(class_name(), build_file.name(),
487         "Could not find the build configuration; is FEISTY_MEOW_APEX set?");
488   }
489   byte_array full_config;
490   build_file.read(full_config, 100000);  // a good chance to be big enough.
491   build_file.close();
492
493 //log("got config info:");
494 //log((char *)full_config.observe());
495
496   astring build_number;
497   ini_configurator temp_ini(fname, configurator::RETURN_ONLY);
498   build_number += temp_ini.load("version", "major", "");
499   build_number += ".";
500   build_number += temp_ini.load("version", "minor", "");
501   build_number += ".";
502   build_number += temp_ini.load("version", "revision", "");
503   build_number += ".";
504   build_number += temp_ini.load("version", "build", "");
505   if (build_number.equal_to("...")) {
506     non_continuable_error(class_name(), build_file.name(),
507         "Could not read the build number; is build parameter file malformed?");
508   }
509
510 //log(astring("got build num: ") + build_number);
511
512   // now that we know what file to create, write the header blob for it.
513   _manifest.open(_manifest_filename, "wb");
514   if (!_manifest.good()) {
515     non_continuable_error(class_name(), _manifest_filename,
516         "Could not write to the manifest file!");
517   }
518   _manifest.write(header_string(build_number));
519
520   // make sure we have the right ordering for our terms.  items that are
521   // non-modify types must come before the modifying types.
522   for (int i = 0; i < _search_list.symbols(); i++) {
523     search_record &curr_reco = _search_list[i];
524     if (curr_reco._no_modify)
525       _search_ordering += i;
526     else
527       _postponed_searches += i;
528   }
529
530   // scan across each directory specified for our first pass.
531   LOG("First pass...");
532   for (int i = 0; i < _dirs.symbols(); i++) {
533     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
534     LOG(astring("  Processing: ") + _dirs.name(i));
535     bool ret = process_tree(_dirs.name(i));
536     if (!ret) {
537       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
538     }
539   }
540   LOG("");
541
542   // second pass now.
543   LOG("Second pass...");
544   _search_ordering = _postponed_searches;  // recharge the list for 2nd pass.
545   _dirs_seen.reset();  // drop any directories we saw before.
546   for (int i = 0; i < _dirs.symbols(); i++) {
547     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
548     LOG(astring("  Processing: ") + _dirs.name(i));
549     bool ret = process_tree(_dirs.name(i));
550     if (!ret) {
551       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
552     }
553   }
554   LOG("");
555
556   const astring quote = "\"";
557   const astring comma = ",";
558
559   // scoot across all the completed searches and dump results.
560   for (int i = 0; i < _search_list.symbols(); i++) {
561     search_record &curr_reco = _search_list[i];
562     const astring &pattern = curr_reco._search;
563
564     _manifest.write(astring("/* START ") + pattern + "\n");
565     _manifest.write(astring("[") + pattern + "]\n");
566
567     if (!curr_reco.is_link()) {
568       // scoot across all definitions and print them out.
569
570       // do the print out in order, as dictated by the sign of the increment.
571       sorting_array sortie;
572       for (int j = 0; j < curr_reco.definitions().symbols(); j++) {
573         const item_record &rec = curr_reco.definitions().get(j);
574         sortie += simple_sorter(j, rec._value);
575       }
576       shell_sort(sortie.access(), sortie.length(),
577           negative(curr_reco.value_increment()));
578
579       for (int j = 0; j < sortie.length(); j++) {
580         int indy = sortie[j]._index;
581         const item_record &rec = curr_reco.definitions().get(indy);
582         astring to_write = "  ";
583         if (rec._extra_tag.t()) {
584           to_write += astring("(") + rec._extra_tag + ") ";
585         }
586         to_write += quote + rec._name + quote + comma + " ";
587         to_write += quote + a_sprintf("%d", rec._value) + quote + comma + " ";
588         to_write += quote + rec._description + quote + comma + " ";
589         to_write += quote + rec._path + quote;
590         to_write += "\n";
591         _manifest.write(to_write);
592       }
593     } else {
594       // this is just a link.
595       astring to_write = "  Linked to search item ";
596       to_write += curr_reco.our_link()->_search;
597       to_write += "\n";
598       _manifest.write(to_write);
599     }
600
601     _manifest.write(astring("END ") + pattern + " */\n\n");
602   }
603
604   _manifest.write(footer_string(full_config));
605
606   // show all the modified files.
607   if (_modified_files.symbols()) {
608     const int syms = _modified_files.symbols();
609     LOG("Modified Files:");
610     LOG("===============");
611     for (int i = 0; i < syms; i++) {
612       LOG(_modified_files.name(i));
613     }
614   } else {
615     LOG("No files needed modification for generated values.");
616   }
617   LOG("");
618
619   log(time_stamp::notarize(true) + "value_tagger finished.", ALWAYS_PRINT);
620
621   return 0;
622 }
623
624 #define INBO (indy < scanning.length())
625   // a macro that makes length checking less verbose.
626
627 // make sure we drop any spaces in between important bits.
628 #define SKIP_SPACES \
629   while (INBO && parser_bits::white_space(scanning[indy])) indy++;
630
631 // return with a failure but say why it happened.
632 #define FAIL_PARSE(why) { \
633   log(astring("failed to parse the string because ") + why + ".", ALWAYS_PRINT); \
634   return false; \
635 }
636
637 bool value_tagger::parse_define(const astring &scanning, int indy,
638     astring &name, int &value, astring &description, int &num_start,
639     int &num_end)
640 {
641   // prepare our result objects.
642   name = ""; value = -1; description = ""; num_start = -1; num_end = -1;
643
644   SKIP_SPACES;
645
646   // look for starting parenthesis.
647   if (!INBO || (scanning[indy] != '(') )
648     FAIL_PARSE("the first parenthesis is missing");
649
650   indy++;  // skip paren.
651   SKIP_SPACES;
652
653   // find the name of the item being defined.
654   while (INBO && (scanning[indy] != ',') ) {
655     name += scanning[indy];
656     indy++;
657   }
658
659   indy++;  // skip the comma.
660   SKIP_SPACES;
661
662   astring num_string;
663   num_start = indy;
664   while (INBO && parser_bits::is_numeric(scanning[indy])) {
665     num_string += scanning[indy];
666     indy++;
667   }
668   num_end = indy - 1;
669   value = num_string.convert(0);
670
671   SKIP_SPACES;
672
673   if (!INBO || (scanning[indy] != ',') )
674     FAIL_PARSE("the post-value comma is missing");
675
676   indy++;
677   SKIP_SPACES;
678
679   if (!INBO || (scanning[indy] != '"') )
680     FAIL_PARSE("the opening quote for the description is missing");
681
682   indy++;  // now we should be at raw text.
683
684   // scan through the full description, taking into account that it might
685   // be broken across multiple lines as several quoted bits.
686   bool in_quote = true;  // we're inside a quote now.
687   while (INBO && (scanning[indy] != ')') ) {
688     const char curr = scanning[indy];
689 //hmmm: escaped quotes are not currently handled.
690     if (curr == '"') in_quote = !in_quote;  // switch quoting state.
691     else if (in_quote) description += curr;
692     indy++;
693   }
694
695   return scanning[indy] == ')';
696 }
697
698 bool value_tagger::process_file(const astring &path)
699 {
700   byte_filer examining(path, "rb");
701   if (!examining.good()) {
702     log(astring("Error reading file: ") + path, ALWAYS_PRINT);
703     return false;
704   }
705   examining.seek(0, byte_filer::FROM_END);
706   int fsize = int(examining.tell());
707   examining.seek(0, byte_filer::FROM_START);
708
709   astring contents('\0', fsize + 20);
710   int bytes_read = examining.read((abyte *)contents.access(), fsize);
711     // read the file directly into a big astring.
712   examining.close();
713   contents[bytes_read] = '\0';
714   contents.shrink();  // drop any extra stuff at end.
715
716   bool modified = false;  // set to true if we need to write the file back.
717
718   // check if the file matches our phrases of interest.
719   bool matched = false;
720   for (int q = 0; q < _search_list.symbols(); q++) {
721     search_record &curr_reco = _search_list[q];
722     if (contents.contains(curr_reco._search)) {
723 //_manifest.write(astring("MATCH-") + curr_pattern + ": " + path + "\n" ); //temp
724       matched = true;
725       break;
726     }
727   }
728
729   if (!matched) return true;
730
731   // now we have verified that there's something interesting in this file.
732   // go through to find the interesting bits.
733
734   // we do this in the search ordering that we established earlier, so we
735   // will tag the values in the proper order.
736   for (int x = 0; x < _search_ordering.length(); x++) {
737     int q = _search_ordering[x];  // get our real index.
738     search_record &curr_reco = _search_list[q];
739     const astring &curr_pattern = curr_reco._search;
740 ///log(astring("now seeking ") + curr_pattern);
741     int start_from = 0;  // where searches will start from.
742
743     while (true) {
744       // search forward for next match.
745       int indy = contents.find(curr_pattern, start_from);
746       if (negative(indy)) break;  // no more matches.
747       start_from = indy + 5;  // ensure we'll skip past the last match.
748
749       // make sure our deadly pattern isn't in front; we don't want to
750       // process the actual definition of the macro in question.
751 //log(a_sprintf("indy=%d [indy-1]=%c [indy-2]=%c", indy, contents[indy-1], contents[indy-2]));
752       if ( (indy > 3) && (contents[indy-1] == ' ')
753           && (contents[indy-2] == 'e') ) {
754         int def_indy = contents.find("#define", indy, true);
755 //log(astring("checking ") + curr_pattern + a_sprintf(": defindy %d, ", def_indy) + path + "\n" );
756
757         if (non_negative(def_indy) && (absolute_value(indy - def_indy) < 12) ) {
758           // they're close enough that we probably need to skip this
759           // occurrence of our search term.
760 //_manifest.write(astring("DEMATCH-") + curr_pattern + ": had the #define! " + path + "\n" );
761           continue;
762         }
763       }
764
765       // make sure we don't include commented lines in consideration.
766       int comm_indy = contents.find("//", indy, true);
767       if (non_negative(comm_indy)) {
768 //log("found a comment marker");
769         // we found a comment before the definition, but we're not sure how
770         // far before.
771         if (absolute_value(comm_indy - indy) < LONGEST_SEPARATION) {
772 //log("comment is close enough...");
773           // they could be on the same line...  unless lines are longer than
774           // our constant.
775           bool found_cr = false;
776           for (int q = comm_indy; q < indy; q++) {
777             if (parser_bits::is_eol(contents[q])) {
778               found_cr = true;
779               break;
780             }
781           }
782           if (!found_cr) {
783             // if there's a comment before the definition and no carriage
784             // returns in between, then this is just a comment.
785 //log(astring("DEMATCH-") + curr_pattern + ": had the comment! " + path + "\n" );
786             continue;
787           }
788         }
789       }
790
791       // now we are pretty sure this is a righteous definition of an outcome,
792       // and not the definition of the macro itself.
793       int value, num_start, num_end;
794       astring name, description;
795       bool found_it = parse_define(contents, indy + curr_pattern.length(),
796           name, value, description, num_start, num_end);
797       if (!found_it) {
798         log(astring("there was a problem parsing ") + curr_pattern + " in " + path, ALWAYS_PRINT);
799         continue;
800       }
801
802       // handle the special keyword for changing the value.  this is useful
803       // if you want a set of outcomes to start at a specific range.
804       if (name.equal_to(SKIP_VALUE_PHRASE)) {
805         LOG(astring("\tSkipping value for ") + curr_pattern
806             + a_sprintf(" to %d because of request in\n\t", value) + path);
807         curr_reco.current_value() = value;
808       }
809       while (true) {
810         // make sure that the current value is not already in use.
811         if (!curr_reco.out_of_band().member(curr_reco.current_value()))
812           break;
813         // if we had a match above, we need to adjust the current value.
814         curr_reco.current_value() += curr_reco.value_increment();
815       }
816       if (name.equal_to(SKIP_VALUE_PHRASE)) {
817         continue;  // keep going now that we vetted the current value.
818       }
819
820 //must catch some conditions here for values:
821 //  for incrementing types, we can always just try to use the next value
822 //  once we know it wasn't already defined out of band?
823 //  for non-incrementing types, we need to ensure we haven't already seen
824 //  the thing.  do we just always add a value seen to out of band?
825 //  for mixed types, the incrementing side needs to not reuse out of band
826 //  values.  
827
828       astring other_place;  // the other place it was defined.
829       if (curr_reco.out_of_band().member(value) && curr_reco._no_modify) {
830         // this is bad; we have already seen this value elsewhere...
831         for (int x = 0; x < curr_reco.definitions().symbols(); x++) {
832           // see if we can find the previous definition in our list.
833           if (value == curr_reco.definitions()[x]._value)
834             other_place = curr_reco.definitions()[x]._path;
835         }
836         non_continuable_error(class_name(), path,
837             a_sprintf("There is a duplicate value here for %s=%d !  "
838                 "Also defined in %s.", name.s(), value, other_place.s()));
839       }
840
841       // we care sometimes that this value is different than the next
842       // sequential one we'd assign.  if it's a non-modifying type of
843       // search, then we can't change the assigned value anyway--we can
844       // only report the error in re-using a value (above).
845       if (!curr_reco._no_modify) {
846         // check that the defined value matches the next one we'd assign.
847         if (value != curr_reco.current_value()) {
848           // patch the value with the appropriate one we've been tracking.
849           modified = true;
850           value = curr_reco.current_value();
851           contents.zap(num_start, num_end);  // remove old fusty value.
852           contents.insert(num_start, a_sprintf("%d", value));
853           _modified_files.add(path, "");
854         }
855         // move the current value up (or down).
856         curr_reco.current_value() += curr_reco.value_increment();
857       } else {
858         // non-modifying type of value here.
859 //anything to do?
860       }
861
862       curr_reco.out_of_band() += value;
863         // we've vetted the value, and now we're definitely using it.
864
865       // make sure they aren't trying to reuse the name for this item.
866       item_record rec;
867       bool found_name = false;  // we don't want to find name already there.
868       if (curr_reco.definitions().find(name)) {
869         rec = *curr_reco.definitions().find(name);
870         found_name = true;
871       }
872       if (found_name) {
873         // this is bad.  this means we are not unique.  remove the manifest
874         // file due to this error.
875         _manifest.close();  // close the file since we want to whack it.
876         filename(_manifest_filename).unlink();
877         non_continuable_error(class_name(), path,
878             a_sprintf("There is a duplicate name here (%s)!  "
879                 "Also defined in %s.", name.s(), rec._path.s()));
880       }
881
882       // record the definition in the appropriate table.
883       curr_reco.definitions().add(name, item_record(name, value,
884           description, path, curr_reco._tag));
885
886 //log(curr_pattern + a_sprintf(": name=%s value=%d desc=[%s]\n", name.s(), value, description.s()));
887
888     }
889   }
890
891   if (modified) {
892     // rewrite the file, since we modified its contents.
893     bool chmod_result = filename(path).chmod(filename::ALLOW_BOTH,
894         filename::USER_RIGHTS);
895 /*
896     int chmod_value;
897 #ifdef __UNIX__
898     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
899 #elif defined(__WIN32__)
900     chmod_value = _S_IREAD | _S_IWRITE;
901 #else
902     //unknown.  let's try unix...
903     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
904 #endif
905     int chmod_result = chmod(path.s(), chmod_value);
906 */
907     if (!chmod_result) {
908       log(astring("there was a problem changing permissions on ") + path
909           + "; writing the new version might fail.", ALWAYS_PRINT);
910     }
911
912     byte_filer rewriting(path, "wb");
913     rewriting.write(contents);
914     rewriting.close();
915   }
916
917   return true;
918 }
919
920 bool value_tagger::process_tree(const astring &path)
921 {
922   directory_tree dir(path, "*.h");
923   if (!dir.good()) return false;
924
925   dir_tree_iterator *ted = dir.start(directory_tree::prefix);
926     // create our iterator to perform a prefix traversal.
927
928   filename curr_dir;  // the current path the iterator is at.
929   string_array files;  // the filenames held at the iterator.
930
931   while (directory_tree::current(*ted, curr_dir, files)) {
932     // we have a good directory to process.
933
934     // omit any subdirectories that exactly match directories we've already
935     // scanned.  necessary to avoid redoing whole areas.
936     if (!_dirs_seen.find(curr_dir)) {
937       // deal with each matching header file we've found.
938       for (int i = 0; i < files.length(); i++) {
939         bool file_ret = process_file(filename(curr_dir.raw(), files[i]));
940         if (!file_ret) {
941           log(astring("There was an error while processing ") + files[i], ALWAYS_PRINT);
942         }
943       }
944
945       _dirs_seen.add(curr_dir, "");
946     }
947
948     // go to the next place.
949     directory_tree::next(*ted);
950   }
951
952   directory_tree::throw_out(ted);
953   return true;
954 }
955
956 HOOPLE_MAIN(value_tagger, )
957
958 #ifdef __BUILD_STATIC_APPLICATION__
959   // static dependencies found by buildor_gen_deps.sh:
960   #include <application/application_shell.cpp>
961   #include <application/command_line.cpp>
962   #include <application/windoze_helper.cpp>
963   #include <basis/astring.cpp>
964   #include <basis/common_outcomes.cpp>
965   #include <basis/environment.cpp>
966   #include <basis/guards.cpp>
967   #include <basis/mutex.cpp>
968   #include <basis/utf_conversion.cpp>
969   #include <configuration/application_configuration.cpp>
970   #include <configuration/configurator.cpp>
971   #include <configuration/ini_configurator.cpp>
972   #include <configuration/ini_parser.cpp>
973   #include <configuration/table_configurator.cpp>
974   #include <configuration/variable_tokenizer.cpp>
975   #include <filesystem/byte_filer.cpp>
976   #include <filesystem/directory.cpp>
977   #include <filesystem/directory_tree.cpp>
978   #include <filesystem/file_info.cpp>
979   #include <filesystem/file_time.cpp>
980   #include <filesystem/filename.cpp>
981   #include <filesystem/filename_list.cpp>
982   #include <filesystem/filename_tree.cpp>
983   #include <filesystem/huge_file.cpp>
984   #include <loggers/combo_logger.cpp>
985   #include <loggers/console_logger.cpp>
986   #include <loggers/critical_events.cpp>
987   #include <loggers/file_logger.cpp>
988   #include <loggers/program_wide_logger.cpp>
989   #include <nodes/node.cpp>
990   #include <nodes/packable_tree.cpp>
991   #include <nodes/path.cpp>
992   #include <nodes/tree.cpp>
993   #include <structures/bit_vector.cpp>
994   #include <structures/checksums.cpp>
995   #include <structures/object_packers.cpp>
996   #include <structures/static_memory_gremlin.cpp>
997   #include <structures/string_hasher.cpp>
998   #include <structures/string_table.cpp>
999   #include <structures/version_record.cpp>
1000   #include <textual/byte_formatter.cpp>
1001   #include <textual/parser_bits.cpp>
1002   #include <textual/string_manipulation.cpp>
1003   #include <timely/earth_time.cpp>
1004   #include <timely/time_stamp.cpp>
1005 #endif // __BUILD_STATIC_APPLICATION__
1006