1 //
2 //      aegis - project change supervisor
3 //      Copyright (C) 1991-2008, 2011, 2012 Peter Miller
4 //
5 //      This program is free software; you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation; either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      This program is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with this program. If not, see
17 //      <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/ac/assert.h>
21 #include <common/ac/errno.h>
22 #include <common/ac/stdio.h>
23 #include <common/ac/stddef.h>
24 #include <common/ac/stdlib.h>
25 #include <common/ac/libintl.h>
26 #include <common/ac/limits.h>
27 #include <common/ac/string.h>
28 #include <common/ac/wchar.h>
29 #include <common/ac/wctype.h>
30 #include <common/ac/grp.h>
31 #include <common/ac/pwd.h>
32 
33 #include <common/arglex.h>
34 #include <common/error.h>
35 #include <common/language.h>
36 #include <common/mem.h>
37 #include <common/page.h>
38 #include <common/progname.h>
39 #include <common/quit.h>
40 #include <common/str_list.h>
41 #include <common/trace.h>
42 #include <common/wstring/list.h>
43 #include <libaegis/change.h>
44 #include <libaegis/change/branch.h>
45 #include <libaegis/change/file.h>
46 #include <libaegis/collect.h>
47 #include <libaegis/file.h>
48 #include <libaegis/gonzo.h>
49 #include <libaegis/option.h>
50 #include <libaegis/os.h>
51 #include <libaegis/project.h>
52 #include <libaegis/project/file.h>
53 #include <libaegis/project/history.h>
54 #include <libaegis/sub.h>
55 #include <libaegis/sub/functor/glue.h>
56 #include <libaegis/sub/functor/hostname.h>
57 #include <libaegis/sub/functor/variable.h>
58 #include <libaegis/sub/addpathsuffi.h>
59 #include <libaegis/sub/architecture.h>
60 #include <libaegis/sub/base_relativ.h>
61 #include <libaegis/sub/basename.h>
62 #include <libaegis/sub/binary_direc.h>
63 #include <libaegis/sub/capitalize.h>
64 #include <libaegis/sub/change/active_direc.h>
65 #include <libaegis/sub/change/attribute.h>
66 #include <libaegis/sub/change/delta.h>
67 #include <libaegis/sub/change/develo_direc.h>
68 #include <libaegis/sub/change/develop_list.h>
69 #include <libaegis/sub/change/developer.h>
70 #include <libaegis/sub/change/files.h>
71 #include <libaegis/sub/change/integr_direc.h>
72 #include <libaegis/sub/change/integrator.h>
73 #include <libaegis/sub/change/number.h>
74 #include <libaegis/sub/change/reviewer.h>
75 #include <libaegis/sub/change/reviewr_list.h>
76 #include <libaegis/sub/change/state.h>
77 #include <libaegis/sub/change/version.h>
78 #include <libaegis/sub/comment.h>
79 #include <libaegis/sub/common_direc.h>
80 #include <libaegis/sub/copyri_owner.h>
81 #include <libaegis/sub/copyri_years.h>
82 #include <libaegis/sub/data_directo.h>
83 #include <libaegis/sub/date.h>
84 #include <libaegis/sub/diff.h>
85 #include <libaegis/sub/dirname.h>
86 #include <libaegis/sub/dirname_rel.h>
87 #include <libaegis/sub/dollar.h>
88 #include <libaegis/sub/downcase.h>
89 #include <libaegis/sub/email_addres.h>
90 #include <libaegis/sub/expr.h>
91 #include <libaegis/sub/getenv.h>
92 #include <libaegis/sub/histo_direc.h>
93 #include <libaegis/sub/history_path.h>
94 #include <libaegis/sub/identifier.h>
95 #include <libaegis/sub/left.h>
96 #include <libaegis/sub/length.h>
97 #include <libaegis/sub/librar_direc.h>
98 #include <libaegis/sub/namemax.h>
99 #include <libaegis/sub/path_reduce.h>
100 #include <libaegis/sub/perl.h>
101 #include <libaegis/sub/plural.h>
102 #include <libaegis/sub/plural_forms.h>
103 #include <libaegis/sub/project.h>
104 #include <libaegis/sub/project/adminis_list.h>
105 #include <libaegis/sub/project/baseline.h>
106 #include <libaegis/sub/project/develop_list.h>
107 #include <libaegis/sub/project/integra_list.h>
108 #include <libaegis/sub/project/reviewe_list.h>
109 #include <libaegis/sub/project/specific.h>
110 #include <libaegis/sub/quote.h>
111 #include <libaegis/sub/read_file.h>
112 #include <libaegis/sub/right.h>
113 #include <libaegis/sub/search_path.h>
114 #include <libaegis/sub/shell.h>
115 #include <libaegis/sub/source.h>
116 #include <libaegis/sub/split.h>
117 #include <libaegis/sub/substitute.h>
118 #include <libaegis/sub/substr.h>
119 #include <libaegis/sub/switch.h>
120 #include <libaegis/sub/trim_directo.h>
121 #include <libaegis/sub/trim_extensi.h>
122 #include <libaegis/sub/unsplit.h>
123 #include <libaegis/sub/upcase.h>
124 #include <libaegis/sub/user.h>
125 #include <libaegis/sub/zero_pad.h>
126 
127 
128 enum getc_type
129 {
130     getc_type_control,
131     getc_type_data
132 };
133 
134 
sub_context_ty(const char * arg1,int arg2)135 sub_context_ty::sub_context_ty(const char *arg1, int arg2) :
136     cp(0),
137     pp(0),
138     suberr(0),
139     errno_sequester(errno),
140     file_name(arg1 ? arg1 : __FILE__),
141     line_number(arg1 ? (arg2 > 0 ? arg2 : 1) : __LINE__)
142 {
143 }
144 
145 
~sub_context_ty()146 sub_context_ty::~sub_context_ty()
147 {
148     clear();
149 }
150 
151 
152 void
error_set(const char * s)153 sub_context_ty::error_set(const char *s)
154 {
155     suberr = s;
156 }
157 
158 
159 project *
project_get()160 sub_context_ty::project_get()
161 {
162     return pp;
163 }
164 
165 
166 change::pointer
change_get()167 sub_context_ty::change_get()
168 {
169     return cp;
170 }
171 
172 
173 //
174 // NAME
175 //      sub_errno - the errno substitution
176 //
177 // SYNOPSIS
178 //      wstring sub_errno(const wstring_list &arg);
179 //
180 // DESCRIPTION
181 //      The sub_errno function implements the errno substitution.  The
182 //      errno substitution is replaced by the value if th errno variable
183 //      provided by the system, as mapped through the strerror function.
184 //
185 //      Requires exactly zero arguments.
186 //
187 //      The sub_errno_setx() function may be used to remember errno,
188 //      and thus isolate the error from subsequest system calls.
189 //
190 // ARGUMENTS
191 //      arg     - list of arguments, including the function name as [0]
192 //
193 // RETURNS
194 //      a pointer to a string in dynamic memory;
195 //      or NULL on error, setting suberr appropriately.
196 //
197 
198 static wstring
sub_errno(sub_context_ty * scp,const wstring_list & arg)199 sub_errno(sub_context_ty *scp, const wstring_list &arg)
200 {
201     trace(("sub_errno()\n{\n"));
202     if (arg.size() != 1)
203     {
204         scp->error_set(i18n("requires zero arguments"));
205         trace(("return NULL;\n"));
206         trace(("}\n"));
207         return wstring();
208     }
209 
210     int err = scp->errno_sequester_get();
211     if (err == EPERM || err == EACCES)
212     {
213         int uid;
214         int gid;
215         os_become_query(&uid, &gid, (int *)0);
216         struct passwd *pw = getpwuid(uid);
217         char uidn[20];
218         if (pw)
219             snprintf(uidn, sizeof(uidn), "user \"%.8s\"", pw->pw_name);
220         else
221             snprintf(uidn, sizeof(uidn), "uid %d", uid);
222 
223         struct group *gr = getgrgid(gid);
224         char gidn[20];
225         if (gr)
226             snprintf(gidn, sizeof(gidn), "group \"%.8s\"", gr->gr_name);
227         else
228             snprintf(gidn, sizeof(gidn), "gid %d", gid);
229 
230         nstring s = nstring::format("%s [%s, %s]", strerror(err), uidn, gidn);
231         wstring result(s);
232         trace(("return %p;\n", result.get_ref()));
233         trace(("}\n"));
234         return result;
235     }
236 
237     wstring result(strerror(err));
238     trace(("return %p;\n", result.get_ref()));
239     trace(("}\n"));
240     return result;
241 }
242 
243 
244 static sub_functor_list globals;
245 
246 
247 static void
init_globals()248 init_globals()
249 {
250     if (!globals.empty())
251         return;
252     globals.push_back(sub_functor_glue::create("$", sub_dollar));
253     globals.push_back(sub_functor_glue::create("#", sub_comment));
254     globals.push_back
255     (
256         sub_functor_glue::create
257         (
258             "Active_Directory",
259             sub_change_active_directory
260         )
261     );
262     globals.push_back
263     (
264         sub_functor_glue::create("Add_Path_Suffix", sub_add_path_suffix)
265     );
266     globals.push_back
267     (
268         sub_functor_glue::create("Administrator_List", sub_administrator_list)
269     );
270     globals.push_back
271     (
272         sub_functor_glue::create("ARCHitecture", sub_architecture)
273     );
274     globals.push_back(sub_functor_glue::create("BaseLine", sub_baseline));
275     globals.push_back(sub_functor_glue::create("Basename", sub_basename));
276     globals.push_back
277     (
278         sub_functor_glue::create("BAse_RElative", sub_base_relative)
279     );
280     globals.push_back
281     (
282         sub_functor_glue::create("BINary_DIRectory", sub_binary_directory)
283     );
284     globals.push_back(sub_functor_glue::create("CAPitalize", sub_capitalize));
285     globals.push_back(sub_functor_glue::create("Change", sub_change_number));
286     globals.push_back
287     (
288         sub_functor_glue::create("Change_Attribute", sub_change_attribute)
289     );
290     globals.push_back
291     (
292         sub_functor_glue::create
293         (
294             "Change_Developer_List",
295             sub_change_developer_list
296         )
297     );
298     globals.push_back
299     (
300         sub_functor_glue::create("Change_Files", sub_change_files)
301     );
302     globals.push_back
303     (
304         sub_functor_glue::create
305         (
306             "Change_Reviewer_List",
307             sub_change_reviewer_list
308         )
309     );
310     globals.push_back
311     (
312         sub_functor_glue::create("Copyright_Owner", sub_copyright_owner)
313     );
314     globals.push_back
315     (
316         sub_functor_glue::create("Copyright_Years", sub_copyright_years)
317     );
318     globals.push_back(sub_functor_glue::create("COMment", sub_comment));
319     globals.push_back
320     (
321         sub_functor_glue::create("COMmon_DIRectory", sub_common_directory)
322     );
323     globals.push_back
324     (
325         sub_functor_glue::create("DATa_DIRectory", sub_data_directory)
326     );
327     globals.push_back(sub_functor_glue::create("DAte", sub_date));
328     globals.push_back(sub_functor_glue::create("DELta", sub_delta));
329     globals.push_back(sub_functor_glue::create("DEVeloper", sub_developer));
330     globals.push_back
331     (
332         sub_functor_glue::create("DEVeloper_List", sub_developer_list)
333     );
334     // Default_Development_Directory
335     globals.push_back
336     (
337         sub_functor_glue::create
338         (
339             "Development_Directory",
340             sub_development_directory
341         )
342     );
343     globals.push_back(sub_functor_glue::create("DIFference", sub_diff));
344     globals.push_back(sub_functor_glue::create("Dirname", sub_dirname));
345     globals.push_back
346     (
347         sub_functor_glue::create("Dirname_RELative", sub_dirname_relative)
348     );
349     globals.push_back(sub_functor_glue::create("DownCase", sub_downcase));
350     globals.push_back(sub_functor_glue::create("DOLlar", sub_dollar));
351     // Edit
352     globals.push_back
353     (
354         sub_functor_glue::create("EMail_Address", sub_email_address)
355     );
356     globals.push_back(sub_functor_glue::create("ENVironment", sub_getenv));
357     globals.push_back(sub_functor_glue::create("ERrno", sub_errno));
358     globals.push_back(sub_functor_glue::create("EXpression", sub_expression));
359     // FieLD_List
360     // File_List
361     // File_Name
362     globals.push_back(sub_functor_glue::create("Get_Environment", sub_getenv));
363     // Guess
364     // History
365     globals.push_back
366     (
367         sub_functor_glue::create("History_Directory", sub_history_directory)
368     );
369     globals.push_back
370     (
371         sub_functor_glue::create("History_Path", sub_history_path)
372     );
373     globals.push_back(sub_functor_hostname::create("HostName"));
374     // Input
375     globals.push_back(sub_functor_glue::create("IDentifier", sub_identifier));
376     globals.push_back
377     (
378         sub_functor_glue::create
379         (
380             "INTegration_Directory",
381             sub_integration_directory
382         )
383     );
384     globals.push_back(sub_functor_glue::create("INTegrator", sub_integrator));
385     globals.push_back
386     (
387         sub_functor_glue::create("INTegrator_List", sub_integrator_list)
388     );
389     globals.push_back(sub_functor_glue::create("LEFt", sub_left));
390     globals.push_back(sub_functor_glue::create("LENgth", sub_length));
391     globals.push_back(sub_functor_glue::create("LIBrary", sub_data_directory));
392     globals.push_back
393     (
394         sub_functor_glue::create("LIBrary_DIRectory", sub_library_directory)
395     );
396     // MAgic
397     // MeSsaGe
398     // Most_Recent
399     globals.push_back(sub_functor_glue::create("Name_Maximum", sub_namemax));
400     // Number
401     // Output
402     // ORiginal
403     globals.push_back(sub_functor_glue::create("PAth_REduce", sub_path_reduce));
404     globals.push_back(sub_functor_glue::create("PERL", sub_perl));
405     globals.push_back(sub_functor_glue::create("PLural", sub_plural));
406     globals.push_back
407     (
408         sub_functor_glue::create("PLural_Forms", sub_plural_forms)
409     );
410     globals.push_back(sub_functor_glue::create("Project", sub_project));
411     globals.push_back
412     (
413         sub_functor_glue::create("Project_Specific", sub_project_specific)
414     );
415     globals.push_back(sub_functor_glue::create("QUote", sub_quote));
416     globals.push_back
417     (
418         sub_functor_glue::create("Read_File", sub_read_file, true)
419     );
420     globals.push_back
421     (
422         sub_functor_glue::create("Read_File_Simple", sub_read_file)
423     );
424     globals.push_back(sub_functor_glue::create("Reviewer", sub_reviewer));
425     globals.push_back
426     (
427         sub_functor_glue::create("Reviewer_List", sub_reviewer_list)
428     );
429     globals.push_back(sub_functor_glue::create("RIght", sub_right));
430     globals.push_back(sub_functor_glue::create("Search_Path", sub_search_path));
431     globals.push_back
432     (
433         sub_functor_glue::create("Search_Path_Executable", sub_search_path)
434     );
435     globals.push_back(sub_functor_glue::create("SHell", sub_shell));
436     globals.push_back(sub_functor_glue::create("Source", sub_source));
437     globals.push_back(sub_functor_glue::create("SPLit", sub_split));
438     globals.push_back(sub_functor_glue::create("STate", sub_state));
439     globals.push_back(sub_functor_glue::create("SUBSTitute", sub_substitute));
440     globals.push_back(sub_functor_glue::create("SUBSTRing", sub_substr));
441     globals.push_back(sub_functor_glue::create("SWitch", sub_switch));
442     globals.push_back
443     (
444         sub_functor_glue::create("Trim_DIRectory", sub_trim_directory)
445     );
446     globals.push_back
447     (
448         sub_functor_glue::create("Trim_EXTension", sub_trim_extension)
449     );
450 
451     // uname is undocumented
452     globals.push_back(sub_functor_glue::create("UName", sub_architecture));
453 
454     globals.push_back(sub_functor_glue::create("UNSplit", sub_unsplit));
455     globals.push_back(sub_functor_glue::create("UpCase", sub_upcase));
456     globals.push_back(sub_functor_glue::create("USer", sub_user));
457     globals.push_back(sub_functor_glue::create("Version", sub_version));
458     globals.push_back(sub_functor_glue::create("Zero_Pad", sub_zero_pad));
459 }
460 
461 
462 void
execute(const wstring_list & arg)463 sub_context_ty::execute(const wstring_list &arg)
464 {
465     trace(("execute()\n{\n"));
466     init_globals();
467     if (arg.empty())
468     {
469         sub_context_ty inner;
470         inner.var_set_charstar("File_Name", file_name);
471         inner.var_set_long("Line_Number", line_number);
472         inner.fatal_intl
473         (
474             i18n("$filename: $linenumber: empty $${} substitution")
475         );
476         // NOTREACHED
477     }
478 
479     //
480     // scan the variables and functions
481     //
482     nstring cmd = arg[0].to_nstring();
483     sub_functor_list hits;
484     globals.match(cmd, hits);
485     var_list.match(cmd, hits);
486 
487     //
488     // figure what to do
489     //
490     switch (hits.size())
491     {
492     case 0:
493         {
494             nstring s3 = arg.unsplit(" ").to_nstring();
495             const char *the_error = i18n("unknown substitution name");
496             sub_context_ty inner(__FILE__, __LINE__);
497             inner.var_set_charstar("File_Name", file_name);
498             inner.var_set_long("Line_Number", line_number);
499             inner.var_set_string("Name", s3);
500             inner.var_set_charstar("MeSsaGe", gettext(the_error));
501             inner.fatal_intl
502             (
503                 i18n
504                 (
505                     "$filename: $linenumber: substitution $${$name} "
506                     "failed: $message"
507                 )
508             );
509             // NOTREACHED
510         }
511 
512     case 1:
513         break;
514 
515     default:
516         {
517             nstring s3 = arg.unsplit(" ").to_nstring();
518             const char *the_error = i18n("ambiguous substitution name");
519             sub_context_ty inner(__FILE__, __LINE__);
520             inner.var_set_charstar("File_Name", file_name);
521             inner.var_set_long("Line_Number", line_number);
522             inner.var_set_string("Name", s3);
523             inner.var_set_charstar("MeSsaGe", gettext(the_error));
524             inner.fatal_intl
525             (
526                 i18n
527                 (
528                     "$filename: $linenumber: substitution $${$name} "
529                     "failed: $message"
530                 )
531             );
532             // NOTREACHED
533         }
534     }
535 
536     //
537     // Do it.
538     //
539     sub_functor::pointer tp = hits[0];
540     wstring_list tmp;
541     tmp.push_back(wstring(tp->name_get()));
542     for (size_t j = 1; j < arg.size(); ++j)
543         tmp.push_back(arg[j]);
544     wstring s = tp->evaluate(this, tmp);
545 
546     //
547     // report errors as they happen
548     //
549     if (suberr)
550     {
551         sub_context_ty inner(__FILE__, __LINE__);
552         inner.var_set_charstar("File_Name", file_name);
553         inner.var_set_long("Line_Number", line_number);
554         inner.var_set_string("Name", tp->name_get());
555         inner.var_set_charstar("MeSsaGe", suberr);
556         inner.fatal_intl
557         (
558             i18n("$filename: $linenumber: substitution $${$name} failed: "
559             "$message")
560         );
561         // NOTREACHED
562     }
563 
564     //
565     // deal with the result
566     //
567     if (!s.empty())
568         diversion_stack.push_back(s, tp->resubstitute());
569     trace(("}\n"));
570 }
571 
572 
573 wchar_t
getc_meta(getc_type & tr)574 sub_context_ty::getc_meta(getc_type &tr)
575 {
576     trace(("sub_getc_meta()\n{\n"));
577     if (diversion_stack.resub_both())
578         tr = getc_type_control;
579     else
580         tr = getc_type_data;
581     wchar_t result = diversion_stack.getch();
582 #ifdef DEBUG
583     if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
584     {
585         trace(("return '%c' %s;\n", (char)result,
586             (tr == getc_type_control ? "control" : "data")));
587     }
588     else
589     {
590         trace(("return %4.4lX %s;\n", (long)result,
591             (tr == getc_type_control ? "control" : "data")));
592     }
593 #endif
594     trace(("}\n"));
595     return result;
596 }
597 
598 
599 void
getc_meta_undo(wchar_t c)600 sub_context_ty::getc_meta_undo(wchar_t c)
601 {
602     trace(("sub_getc_meta_undo(%ld)\n{\n", (long)c));
603 #ifdef DEBUG
604     if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
605         trace(("c = '%c'\n", (char)c));
606 #endif
607     diversion_stack.ungetch(c);
608     trace(("}\n"));
609 }
610 
611 
612 wchar_t
dollar()613 sub_context_ty::dollar()
614 {
615     trace(("dollar()\n{\n"));
616     collect tmp;
617     wstring_list arg;
618     int result = 0;
619     getc_type ct;
620     wchar_t c = getc_meta(ct);
621     if (ct != getc_type_control)
622         goto normal;
623     switch (c)
624     {
625     case '0':
626     case '1':
627     case '2':
628     case '3':
629     case '4':
630     case '5':
631     case '6':
632     case '7':
633     case '8':
634     case '9':
635         {
636             for (;;)
637             {
638                 tmp.append(c);
639                 c = getc_meta(ct);
640                 switch (c)
641                 {
642                 case '0':
643                 case '1':
644                 case '2':
645                 case '3':
646                 case '4':
647                 case '5':
648                 case '6':
649                 case '7':
650                 case '8':
651                 case '9':
652                     continue;
653 
654                 default:
655                     getc_meta_undo(c);
656                     break;
657                 }
658                 break;
659             }
660             wstring s = tmp.end();
661             trace(("push arg\n"));
662             arg.push_back(s);
663             execute(arg);
664         }
665         break;
666 
667     case 'a':
668     case 'b':
669     case 'c':
670     case 'd':
671     case 'e':
672     case 'f':
673     case 'g':
674     case 'h':
675     case 'i':
676     case 'j':
677     case 'k':
678     case 'l':
679     case 'm':
680     case 'n':
681     case 'o':
682     case 'p':
683     case 'q':
684     case 'r':
685     case 's':
686     case 't':
687     case 'u':
688     case 'v':
689     case 'w':
690     case 'x':
691     case 'y':
692     case 'z':
693     case 'A':
694     case 'B':
695     case 'C':
696     case 'D':
697     case 'E':
698     case 'F':
699     case 'G':
700     case 'H':
701     case 'I':
702     case 'J':
703     case 'K':
704     case 'L':
705     case 'M':
706     case 'N':
707     case 'O':
708     case 'P':
709     case 'Q':
710     case 'R':
711     case 'S':
712     case 'T':
713     case 'U':
714     case 'V':
715     case 'W':
716     case 'X':
717     case 'Y':
718     case 'Z':
719         {
720             for (;;)
721             {
722                 tmp.append(c);
723                 c = getc_meta(ct);
724                 switch (c)
725                 {
726                 case 'a':
727                 case 'b':
728                 case 'c':
729                 case 'd':
730                 case 'e':
731                 case 'f':
732                 case 'g':
733                 case 'h':
734                 case 'i':
735                 case 'j':
736                 case 'k':
737                 case 'l':
738                 case 'm':
739                 case 'n':
740                 case 'o':
741                 case 'p':
742                 case 'q':
743                 case 'r':
744                 case 's':
745                 case 't':
746                 case 'u':
747                 case 'v':
748                 case 'w':
749                 case 'x':
750                 case 'y':
751                 case 'z':
752                 case 'A':
753                 case 'B':
754                 case 'C':
755                 case 'D':
756                 case 'E':
757                 case 'F':
758                 case 'G':
759                 case 'H':
760                 case 'I':
761                 case 'J':
762                 case 'K':
763                 case 'L':
764                 case 'M':
765                 case 'N':
766                 case 'O':
767                 case 'P':
768                 case 'Q':
769                 case 'R':
770                 case 'S':
771                 case 'T':
772                 case 'U':
773                 case 'V':
774                 case 'W':
775                 case 'X':
776                 case 'Y':
777                 case 'Z':
778                 case '0':
779                 case '1':
780                 case '2':
781                 case '3':
782                 case '4':
783                 case '5':
784                 case '6':
785                 case '7':
786                 case '8':
787                 case '9':
788                 case '_':
789                 case '-':
790                     continue;
791 
792                 default:
793                     getc_meta_undo(c);
794                     break;
795                 }
796                 break;
797             }
798             wstring s = tmp.end();
799             trace(("push arg\n"));
800             arg.push_back(s);
801             execute(arg);
802         }
803         break;
804 
805     case '{':
806         c = getch(ct);
807         for (;;)
808         {
809             //
810             // look for terminator
811             //
812             if (c == '}' && ct == getc_type_control)
813                 break;
814 
815             //
816             // watch out for unterminated substitutions
817             //
818             if (!c)
819             {
820                 sub_context_ty inner(__FILE__, __LINE__);
821                 assert(file_name);
822                 inner.var_set_charstar("File_Name", file_name);
823                 assert(line_number);
824                 inner.var_set_long("Line_Number", line_number);
825                 inner.fatal_intl
826                 (
827                   i18n("$filename: $linenumber: unterminated $${} substitution")
828                 );
829                 // NOTREACHED
830                 break;
831             }
832 
833             //
834             // skip white space separating the arguments
835             //
836             if (iswspace(c))
837             {
838                 c = getch(ct);
839                 continue;
840             }
841 
842             //
843             // collect the argument
844             //      any run of non-white-space characters
845             //
846             wchar_t quoted = 0;
847             for (;;)
848             {
849                 if (!c)
850                 {
851                     if (quoted)
852                     {
853                         sub_context_ty inner(__FILE__, __LINE__);
854                         assert(file_name);
855                         inner.var_set_charstar("File_Name", file_name);
856                         assert(line_number);
857                         inner.var_set_long("Line_Number", line_number);
858                         inner.fatal_intl
859                         (
860                         i18n("$filename: $linenumber: unterminated $${} quotes")
861                         );
862                         // NOTREACHED
863                     }
864                     break;
865                 }
866                 if
867                 (
868                     !quoted
869                 &&
870                     (
871                         iswspace(c)
872                     ||
873                         (ct == getc_type_control && c == '}')
874                     )
875                 )
876                     break;
877                 if (c == quoted)
878                 {
879                     assert(quoted);
880                     quoted = 0;
881                 }
882                 else if (c == '\'' && ct == getc_type_control)
883                 {
884                     assert(!quoted);
885                     quoted = c;
886                 }
887                 else if (c == '\\' && ct == getc_type_control)
888                 {
889                     c = getch(ct);
890                     if (!c)
891                     {
892                         sub_context_ty inner(__FILE__, __LINE__);
893                         assert(file_name);
894                         inner.var_set_charstar("File_Name", file_name);
895                         assert(line_number);
896                         inner.var_set_long("Line_Number", line_number);
897                         inner.fatal_intl
898                         (
899                    i18n("$filename: $linenumber: unterminated $${} \\ sequence")
900                         );
901                         // NOTREACHED
902                     }
903                     tmp.append(c);
904                 }
905                 else
906                     tmp.append(c);
907                 c = getch(ct);
908             }
909             wstring s = tmp.end();
910             trace(("push arg\n"));
911             arg.push_back(s);
912         }
913         execute(arg);
914         break;
915 
916     case '$':
917         result = '$';
918         break;
919 
920     case '#':
921         for (;;)
922         {
923             c = getc_meta(ct);
924             if (!c || (c == '\n' && ct == getc_type_control))
925                 break;
926         }
927         result = 0;
928         break;
929 
930     default:
931         normal:
932         getc_meta_undo(c);
933         result = '$';
934         break;
935     }
936 #ifdef DEBUG
937     if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
938         trace(("return '%c';\n", (char)result));
939     else
940         trace(("return %4.4lX;\n", (long)result));
941 #endif
942     trace(("}\n"));
943     return result;
944 }
945 
946 
947 wchar_t
getch(getc_type & tr)948 sub_context_ty::getch(getc_type &tr)
949 {
950     trace(("sub_getc()\n{\n"));
951     wchar_t c = 0;
952     for (;;)
953     {
954         c = getc_meta(tr);
955         if (c && tr != getc_type_control)
956             break;
957         switch (c)
958         {
959         default:
960             break;
961 
962         case 0:
963             if (diversion_stack.empty())
964                 break;
965             diversion_stack.pop_back();
966             continue;
967 
968         case '$':
969             if (!diversion_stack.resub_both())
970                 break;
971             c = dollar();
972             if (!c)
973                 continue;
974             tr = getc_type_data;
975             break;
976         }
977         break;
978     }
979 #ifdef DEBUG
980     if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
981     {
982         trace(("return '%c' %s;\n", (char)c,
983             (tr == getc_type_control ? "control" : "data")));
984     }
985     else
986     {
987         trace(("return 0x%lX; /* %s */\n", (long)c,
988             (tr == getc_type_control ? "control" : "data")));
989     }
990 #endif
991     trace(("}\n"));
992     return c;
993 }
994 
995 
996 void
subst_intl_project(project * a)997 sub_context_ty::subst_intl_project(project *a)
998 {
999     if (a != pp)
1000     {
1001         assert(!pp);
1002         assert(!cp);
1003         pp = a;
1004         cp = 0;
1005     }
1006 }
1007 
1008 
1009 void
subst_intl_change(change::pointer a)1010 sub_context_ty::subst_intl_change(change::pointer a)
1011 {
1012     assert(!pp);
1013     assert(!cp);
1014     pp = a->pp;
1015     cp = a;
1016 }
1017 
1018 
1019 wstring
subst(const wstring & s)1020 sub_context_ty::subst(const wstring &s)
1021 {
1022     trace(("subst(s = %p)\n{\n", s.get_ref()));
1023     collect buf;
1024     diversion_stack.push_back(s, true);
1025     for (;;)
1026     {
1027         //
1028         // get the next character
1029         //
1030         getc_type ct;
1031         wchar_t c = getch(ct);
1032         if (!c)
1033             break;
1034 
1035         //
1036         // save the character
1037         //
1038         buf.append(c);
1039     }
1040 
1041     //
1042     // find any unused variables marked "append if unused"
1043     //
1044     for (size_t j = 0; j < var_list.size(); ++j)
1045     {
1046         sub_functor::pointer tp = var_list[j];
1047         if (!tp->append_if_unused())
1048             continue;
1049         if (!tp->must_be_used())
1050             continue;
1051 
1052         //
1053         // append to the buffer, separated by a space
1054         //
1055         buf.append(L' ');
1056         wstring_list args;
1057         buf.push_back(tp->evaluate(this, args));
1058     }
1059 
1060     //
1061     // find any unused variables
1062     // and complain about them
1063     //
1064     int error_count = 0;
1065     for (size_t j = 0; j < var_list.size(); ++j)
1066     {
1067         sub_functor::pointer tp = var_list[j];
1068         if (!tp->must_be_used())
1069             continue;
1070 
1071         //
1072         // Make sure the variables of this message are optional,
1073         // to avoid infinite loops if there is a mistake in the
1074         // translation string.
1075         //
1076         sub_context_ty inner(__FILE__, __LINE__);
1077         assert(file_name);
1078         inner.var_set_charstar("File_Name", file_name);
1079         assert(line_number);
1080         inner.var_set_long("Line_Number", line_number);
1081         inner.var_set_string("MeSsaGe", s.to_nstring());
1082         inner.var_optional("MeSsaGe");
1083         inner.var_set_string("Name", "$" + tp->name_get());
1084         inner.var_optional("Name");
1085         inner.error_intl
1086         (
1087             i18n
1088             (
1089                 "$filename: $linenumber: in substitution \"$message\" "
1090                 "variable \"$name\" unused"
1091             )
1092         );
1093         ++error_count;
1094     }
1095     if (error_count > 0)
1096     {
1097         //
1098         // Make sure the variables of this message are optional,
1099         // to avoid infinite loops if there is a mistake in the
1100         // translation string.
1101         //
1102         sub_context_ty inner(__FILE__, __LINE__);
1103         assert(file_name);
1104         inner.var_set_charstar("File_Name", file_name);
1105         assert(line_number);
1106         inner.var_set_long("Line_Number", line_number);
1107         inner.var_set_string("MeSsaGe", s.to_nstring());
1108         inner.var_optional("MeSsaGe");
1109         inner.var_set_long("Number", error_count);
1110         inner.var_optional("Number");
1111         inner.fatal_intl
1112         (
1113             i18n
1114             (
1115                 "$filename: $linenumber: in substitution \"$message\" "
1116                 "found unused variables"
1117             )
1118         );
1119         // NOTREACHED
1120     }
1121 
1122     //
1123     // clear the slate, ready for the next run
1124     //
1125     clear();
1126     wstring result = buf.end();
1127     trace(("return %p;\n", result.get_ref()));
1128     trace(("}\n"));
1129     return result;
1130 }
1131 
1132 
1133 wstring
subst_intl_wide(const char * msg)1134 sub_context_ty::subst_intl_wide(const char *msg)
1135 {
1136     trace(("subst_intl_wide(msg = \"%s\")\n{\n", msg));
1137     language_human();
1138     const char *tmp = gettext(msg);
1139     language_C();
1140 #if 0
1141 #ifdef HAVE_GETTEXT
1142     if (tmp == msg)
1143     {
1144         error_raw
1145         (
1146             "%s: %d: warning: message \"%s\" has no translation",
1147             file_name,
1148             line_number,
1149             msg
1150         );
1151     }
1152 #endif // HAVE_GETTEXT
1153 #endif // DEBUG
1154     wstring s(tmp);
1155     wstring result = subst(s);
1156     trace(("return %p;\n", result.get_ref()));
1157     trace(("}\n"));
1158     return result;
1159 }
1160 
1161 
1162 string_ty *
subst_intl(const char * s)1163 sub_context_ty::subst_intl(const char *s)
1164 {
1165     trace(("subst_intl(s = \"%s\")\n{\n", s));
1166     wstring result_wide = subst_intl_wide(s);
1167     string_ty *result = wstr_to_str(result_wide.get_ref());
1168     trace(("return \"%s\";\n", result->str_text));
1169     trace(("}\n"));
1170     return result;
1171 }
1172 
1173 
1174 string_ty *
substitute(change::pointer acp,string_ty * s)1175 sub_context_ty::substitute(change::pointer acp, string_ty *s)
1176 {
1177     trace(("substitute(acp = %p, s = \"%s\")\n{\n", acp, s->str_text));
1178     assert(acp);
1179     subst_intl_change(acp);
1180     wstring wis(s->str_text);
1181     wstring result_wide = subst(wis);
1182     string_ty *result = wstr_to_str(result_wide.get_ref());
1183     trace(("return \"%s\";\n", result->str_text));
1184     trace(("}\n"));
1185     return result;
1186 }
1187 
1188 
1189 string_ty *
substitute_p(project * app,string_ty * s)1190 sub_context_ty::substitute_p(project *app, string_ty *s)
1191 {
1192     trace(("substitute(pp = %p, s = \"%s\")\n{\n", app, s->str_text));
1193     assert(app);
1194     subst_intl_project(app);
1195     wstring wis(s->str_text, s->str_length);
1196     wstring result_wide = subst(wis);
1197     string_ty *result = wstr_to_str(result_wide.get_ref());
1198     trace(("return \"%s\";\n", result->str_text));
1199     trace(("}\n"));
1200     return result;
1201 }
1202 
1203 
1204 void
clear()1205 sub_context_ty::clear()
1206 {
1207     var_list.clear();
1208     cp = 0;
1209     pp = 0;
1210     errno_sequester = 0;
1211 }
1212 
1213 
1214 void
var_set_vformat(const char * name,const char * fmt,va_list ap)1215 sub_context_ty::var_set_vformat(const char *name, const char *fmt, va_list ap)
1216 {
1217     string_ty *s = str_vformat(fmt, ap);
1218     var_set_string(name, s);
1219     str_free(s);
1220 }
1221 
1222 
1223 void
var_set_format(const char * name,const char * fmt,...)1224 sub_context_ty::var_set_format(const char *name, const char *fmt, ...)
1225 {
1226     va_list ap;
1227     va_start(ap, fmt);
1228     var_set_vformat(name, fmt, ap);
1229     va_end(ap);
1230 }
1231 
1232 
1233 void
var_set_string(const char * name,const nstring & value)1234 sub_context_ty::var_set_string(const char *name, const nstring &value)
1235 {
1236     var_list.push_back(sub_functor_variable::create(name, value));
1237 }
1238 
1239 
1240 void
var_set_string(const char * name,string_ty * value)1241 sub_context_ty::var_set_string(const char *name, string_ty *value)
1242 {
1243     var_set_string(name, nstring(value));
1244 }
1245 
1246 
1247 void
var_resubstitute(const char * name)1248 sub_context_ty::var_resubstitute(const char *name)
1249 {
1250     sub_functor::pointer tp = var_list.find(name);
1251     if (tp)
1252         tp->resubstitute_set();
1253     else
1254         this_is_a_bug();
1255 }
1256 
1257 
1258 void
var_override(const char * name)1259 sub_context_ty::var_override(const char *name)
1260 {
1261     sub_functor::pointer tp = var_list.find(name);
1262     if (tp)
1263         tp->override_set();
1264     else
1265         this_is_a_bug();
1266 }
1267 
1268 
1269 void
var_optional(const char * name)1270 sub_context_ty::var_optional(const char *name)
1271 {
1272     sub_functor::pointer tp = var_list.find(name);
1273     if (tp)
1274         tp->optional_set();
1275     else
1276         this_is_a_bug();
1277 }
1278 
1279 
1280 void
var_append_if_unused(const char * name)1281 sub_context_ty::var_append_if_unused(const char *name)
1282 {
1283     sub_functor::pointer tp = var_list.find(name);
1284     if (tp)
1285         tp->append_if_unused_set();
1286     else
1287         this_is_a_bug();
1288 }
1289 
1290 
1291 //
1292 // NAME
1293 //      wrap - wrap s string over lines
1294 //
1295 // SYNOPSIS
1296 //      void wrap(const wstring &);
1297 //
1298 // DESCRIPTION
1299 //      The wrap function is used to print error messages onto stderr
1300 //      wrapping ling lines.  Be very careful of multi-byte characters
1301 //      in international character sets.
1302 //
1303 // CAVEATS
1304 //      Line length is assumed to be 80 characters.
1305 //
1306 
1307 static void
wrap(const wchar_t * s)1308 wrap(const wchar_t *s)
1309 {
1310     const char      *progname;
1311     int             page_width;
1312     int             first_line;
1313     int             nbytes;
1314     static int      progname_width;
1315     int             midway;
1316 
1317     //
1318     // flush any pending output,
1319     // so the error message appears in a sensible place.
1320     //
1321     if (fflush(stdout) || ferror(stdout))
1322         nfatal("(stdout)");
1323 
1324     //
1325     // Ask the system how wide the terminal is.
1326     // Don't use last column, many terminals are dumb.
1327     //
1328     page_width = page_width_get(-1) - 1;
1329     midway = (page_width + 8) / 2;
1330 
1331     //
1332     // Because it must be a legal UNIX file name, it is unlikely to
1333     // be stupid - unprintable characters are hard to type, and most
1334     // file systems don't allow high-bit-on characters in file
1335     // names.  Thus, assume progname is all legal characters.
1336     //
1337     progname = progname_get();
1338     if (!progname_width)
1339     {
1340         wstring wis = wstr_from_c(progname);
1341         progname_width = wis.column_width();
1342     }
1343 
1344     //
1345     // the message is for a human, so
1346     // use the human's locale
1347     //
1348     language_human();
1349 
1350     //
1351     // Emit the message a line at a time, wrapping as we go.  The
1352     // first line starts with the program name, subsequent lines are
1353     // indented by a tab.
1354     //
1355     first_line = 1;
1356     while (*s)
1357     {
1358         const wchar_t   *ep;
1359         int             ocol;
1360         const wchar_t   *break_space;
1361         int             break_space_col;
1362         const wchar_t   *break_punct;
1363         int             break_punct_col;
1364 
1365         //
1366         // Work out how many characters fit on the line.
1367         //
1368         if (first_line)
1369             ocol = progname_width + 2;
1370         else
1371             ocol = 8;
1372 
1373         wctomb(NULL, 0);
1374         ep = s;
1375         break_space = 0;
1376         break_space_col = 0;
1377         break_punct = 0;
1378         break_punct_col = 0;
1379         while (*ep)
1380         {
1381             char            dummy[MB_LEN_MAX];
1382             int             cw;
1383             wchar_t         c;
1384 
1385             //
1386             // Keep printing characters.  Use a dummy
1387             // character for unprintable sequences (which
1388             // should not happen).
1389             //
1390             c = *ep;
1391             if (!iswprint(c))
1392                 c = '?';
1393             nbytes = wctomb(dummy, c);
1394 
1395             cw = wcwidth(c);
1396             if (nbytes <= 0)
1397             {
1398                 //
1399                 // This should not happen!  All
1400                 // unprintable characters should have
1401                 // been turned into C escapes inside the
1402                 // common/wstr.c file when converting from C
1403                 // string to wide strings.
1404                 //
1405                 // Replace invalid wide characters with
1406                 // a C escape.
1407                 //
1408                 cw = 4;
1409                 nbytes = 4;
1410 
1411                 //
1412                 // The wctomb state will be "error",
1413                 // so reset it and brave the worst.  No
1414                 // need to reset the wctomb state, it is
1415                 // not broken.
1416                 //
1417                 wctomb(NULL, 0);
1418             }
1419 
1420             //
1421             // Keep track of good places to break the line,
1422             // but try to avoid runs of white space.  There
1423             // is a pathological case where the line is
1424             // entirely composed of white space, but it does
1425             // not happen often.
1426             //
1427             if (c == ' ')
1428             {
1429                 break_space = ep;
1430                 break_space_col = ocol;
1431                 while (break_space > s && break_space[-1] == ' ')
1432                 {
1433                     --break_space;
1434                     --break_space_col;
1435                 }
1436             }
1437             if (iswpunct(c) && ocol + cw <= page_width)
1438             {
1439                 break_punct = ep + 1;
1440                 break_punct_col = ocol + cw;
1441             }
1442 
1443             //
1444             // if we have run out of room, break here
1445             //
1446             if (ocol + cw > page_width)
1447                 break;
1448             ocol += cw;
1449             ++ep;
1450         }
1451 
1452         //
1453         // see if there is a better place to break the line
1454         //
1455         // Break the line at space characters, otherwise break
1456         // at punctuator characters.  If it is possible to break
1457         // on either a space or a punctuator, choose the space.
1458         //
1459         // However, if the space is in the left half of the
1460         // line, things look very unbalanced, so break on a
1461         // punctuator in that case.
1462         //
1463         if (*ep && *ep != ' ')
1464         {
1465             if (break_space == s)
1466                 break_space = 0;
1467             if
1468             (
1469                 break_space
1470             &&
1471                 break_punct
1472             &&
1473                 break_space_col < midway
1474             &&
1475                 break_punct_col >= midway
1476             )
1477                 ep = break_punct;
1478             else if (break_space)
1479                 ep = break_space;
1480             else if (break_punct)
1481                 ep = break_punct;
1482         }
1483 
1484         //
1485         // print the line
1486         //
1487         char tmp[(MAX_PAGE_WIDTH + 2) * MB_LEN_MAX];
1488         char *tp = tmp;
1489         if (first_line)
1490         {
1491             tp = strendcpy(tp, progname, tmp + sizeof(tmp));
1492             tp = strendcpy(tp, ": ", tmp + sizeof(tmp));
1493         }
1494         else
1495             tp = strendcpy(tp, "\t", tmp + sizeof(tmp));
1496 
1497         //
1498         // Turn the input into a multi bytes chacacters.
1499         //
1500         wctomb(NULL, 0);
1501         while (s < ep)
1502         {
1503             wchar_t         c;
1504 
1505             //
1506             // Keep printing characters.  Use a dummy
1507             // character for unprintable sequences (which
1508             // should not happen).
1509             //
1510             c = *s++;
1511             if (!iswprint(c))
1512                 c = '?';
1513             nbytes = wctomb(tp, c);
1514 
1515             if (nbytes <= 0)
1516             {
1517                 //
1518                 // This should not happen!  All
1519                 // unprintable characters should have
1520                 // been turned into C escapes inside the
1521                 // wstring.c file when converting from C
1522                 // string to wide strings.
1523                 //
1524                 // Replace invalid wide characters with
1525                 // a C escape.
1526                 //
1527                 nbytes = 4;
1528                 tp[0] = '\\';
1529                 tp[1] = '0' + ((c >> 6) & 7);
1530                 tp[2] = '0' + ((c >> 3) & 7);
1531                 tp[3] = '0' + (c & 7);
1532 
1533                 //
1534                 // The wctomb state will be "error",
1535                 // so reset it and brave the worst.  No
1536                 // need to reset the wctomb state, it is
1537                 // not broken.
1538                 //
1539                 wctomb(NULL, 0);
1540             }
1541             tp += nbytes;
1542         }
1543 
1544         //
1545         // Add a newline and end any outstanding shift state and
1546         // add a NUL character.
1547         //
1548         nbytes = wctomb(tp, (wchar_t)'\n');
1549         if (nbytes > 0)
1550             tp += nbytes;
1551         nbytes = wctomb(tp, (wchar_t)0);
1552         if (nbytes > 0)
1553             tp += nbytes;
1554 
1555         //
1556         // Emit the line to stderr.  It is important to do this
1557         // a whole line at a time, otherwise performance is
1558         // terrible - stderr by default is character buffered.
1559         //
1560         fputs(tmp, stderr);
1561         if (ferror(stderr))
1562             break;
1563 
1564         //
1565         // skip leading spaces for subsequent lines
1566         //
1567         while (*s == ' ')
1568             ++s;
1569         first_line = 0;
1570     }
1571 
1572     //
1573     // done with humans
1574     //
1575     language_C();
1576 
1577     //
1578     // make sure nothing went wrong
1579     //
1580     if (fflush(stderr) || ferror(stderr))
1581         nfatal("(stderr)");
1582 }
1583 
1584 
1585 void
error_intl(const char * s)1586 sub_context_ty::error_intl(const char *s)
1587 {
1588     if (!os_testing_mode())
1589         language_check_translations();
1590     wstring message = subst_intl_wide(s);
1591     wrap(message.c_str());
1592 }
1593 
1594 
1595 void
fatal_intl(const char * s)1596 sub_context_ty::fatal_intl(const char *s)
1597 {
1598     if (!os_testing_mode())
1599         language_check_translations();
1600 
1601     //
1602     // Make sure that there isn't an infinite loop,
1603     // if there is a problem with a substitution
1604     // in an error message.
1605     //
1606     static const char *double_jeopardy;
1607     if (double_jeopardy)
1608     {
1609         //
1610         // this error message can't be internationalized
1611         //
1612         fatal_raw
1613         (
1614             "a fatal_intl error (\"%s\") happened while attempting "
1615                 "to report an earlier fatal_intl error (\"%s\").  "
1616                 "This is probably a bug.",
1617             s,
1618             double_jeopardy
1619         );
1620     }
1621     double_jeopardy = s;
1622 
1623     wstring message = subst_intl_wide(s);
1624     wrap(message.c_str());
1625     double_jeopardy = 0;
1626     quit(1);
1627     // NOTREACHED
1628 }
1629 
1630 
1631 void
verbose_intl(const char * s)1632 sub_context_ty::verbose_intl(const char *s)
1633 {
1634     if (option_verbose_get())
1635     {
1636         if (!os_testing_mode())
1637             language_check_translations();
1638         wstring message = subst_intl_wide(s);
1639         wrap(message.c_str());
1640     }
1641     clear();
1642 }
1643 
1644 
1645 void
errno_setx(int x)1646 sub_context_ty::errno_setx(int x)
1647 {
1648     errno_sequester = x;
1649 }
1650 
1651 
1652 int
errno_sequester_get() const1653 sub_context_ty::errno_sequester_get()
1654     const
1655 {
1656     return errno_sequester;
1657 }
1658 
1659 
1660 // vim: set ts=8 sw=4 et :
1661