func.cc revision f23ae8caf11bf0d37d5669603224b76638fc74d4
1// Copyright 2015 Google Inc. All rights reserved
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build ignore
16
17#include "func.h"
18
19#include <errno.h>
20#include <limits.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <unistd.h>
24
25#include <algorithm>
26#include <iterator>
27#include <memory>
28#include <unordered_map>
29
30#include "eval.h"
31#include "fileutil.h"
32#include "find.h"
33#include "log.h"
34#include "parser.h"
35#include "stats.h"
36#include "stmt.h"
37#include "strutil.h"
38#include "symtab.h"
39#include "var.h"
40
41namespace {
42
43// TODO: This code is very similar to
44// NinjaGenerator::TranslateCommand. Factor them out.
45void StripShellComment(string* cmd) {
46  if (cmd->find('#') == string::npos)
47    return;
48
49  string res;
50  bool prev_backslash = false;
51  // Set space as an initial value so the leading comment will be
52  // stripped out.
53  char prev_char = ' ';
54  char quote = 0;
55  bool done = false;
56  const char* in = cmd->c_str();
57  for (; *in && !done; in++) {
58    switch (*in) {
59      case '#':
60        if (quote == 0 && isspace(prev_char)) {
61          while (in[1] && *in != '\n')
62            in++;
63          break;
64        }
65
66      case '\'':
67      case '"':
68      case '`':
69        if (quote) {
70          if (quote == *in)
71            quote = 0;
72        } else if (!prev_backslash) {
73          quote = *in;
74        }
75        res += *in;
76        break;
77
78      case '\\':
79        res += '\\';
80        break;
81
82      default:
83        res += *in;
84    }
85
86    if (*in == '\\') {
87      prev_backslash = !prev_backslash;
88    } else {
89      prev_backslash = false;
90    }
91
92    prev_char = *in;
93  }
94  cmd->swap(res);
95}
96
97void PatsubstFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
98  const string&& pat_str = args[0]->Eval(ev);
99  const string&& repl = args[1]->Eval(ev);
100  const string&& str = args[2]->Eval(ev);
101  WordWriter ww(s);
102  Pattern pat(pat_str);
103  for (StringPiece tok : WordScanner(str)) {
104    ww.MaybeAddWhitespace();
105    pat.AppendSubst(tok, repl, s);
106  }
107}
108
109void StripFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
110  const string&& str = args[0]->Eval(ev);
111  WordWriter ww(s);
112  for (StringPiece tok : WordScanner(str)) {
113    ww.Write(tok);
114  }
115}
116
117void SubstFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
118  const string&& pat = args[0]->Eval(ev);
119  const string&& repl = args[1]->Eval(ev);
120  const string&& str = args[2]->Eval(ev);
121  if (pat.empty()) {
122    *s += str;
123    *s += repl;
124    return;
125  }
126  size_t index = 0;
127  while (index < str.size()) {
128    size_t found = str.find(pat, index);
129    if (found == string::npos)
130      break;
131    AppendString(StringPiece(str).substr(index, found - index), s);
132    AppendString(repl, s);
133    index = found + pat.size();
134  }
135  AppendString(StringPiece(str).substr(index), s);
136}
137
138void FindstringFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
139  const string&& find = args[0]->Eval(ev);
140  const string&& in = args[1]->Eval(ev);
141  if (in.find(find) != string::npos)
142    AppendString(find, s);
143}
144
145void FilterFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
146  const string&& pat_buf = args[0]->Eval(ev);
147  const string&& text = args[1]->Eval(ev);
148  vector<Pattern> pats;
149  for (StringPiece pat : WordScanner(pat_buf)) {
150    pats.push_back(Pattern(pat));
151  }
152  WordWriter ww(s);
153  for (StringPiece tok : WordScanner(text)) {
154    for (const Pattern& pat : pats) {
155      if (pat.Match(tok)) {
156        ww.Write(tok);
157        break;
158      }
159    }
160  }
161}
162
163void FilterOutFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
164  const string&& pat_buf = args[0]->Eval(ev);
165  const string&& text = args[1]->Eval(ev);
166  vector<Pattern> pats;
167  for (StringPiece pat : WordScanner(pat_buf)) {
168    pats.push_back(Pattern(pat));
169  }
170  WordWriter ww(s);
171  for (StringPiece tok : WordScanner(text)) {
172    bool matched = false;
173    for (const Pattern& pat : pats) {
174      if (pat.Match(tok)) {
175        matched = true;
176        break;
177      }
178    }
179    if (!matched)
180      ww.Write(tok);
181  }
182}
183
184void SortFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
185  const string&& list = args[0]->Eval(ev);
186  vector<StringPiece> toks;
187  WordScanner(list).Split(&toks);
188  sort(toks.begin(), toks.end());
189  WordWriter ww(s);
190  StringPiece prev;
191  for (StringPiece tok : toks) {
192    if (prev != tok) {
193      ww.Write(tok);
194      prev = tok;
195    }
196  }
197}
198
199static int GetNumericValueForFunc(const string& buf) {
200  StringPiece s = TrimLeftSpace(buf);
201  char* end;
202  long n = strtol(s.data(), &end, 10);
203  if (n < 0 || n == LONG_MAX || s.data() + s.size() != end) {
204    return -1;
205  }
206  return n;
207}
208
209void WordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
210  const string&& n_str = args[0]->Eval(ev);
211  int n = GetNumericValueForFunc(n_str);
212  if (n < 0) {
213    ev->Error(StringPrintf(
214        "*** non-numeric first argument to `word' function: '%s'.",
215        n_str.c_str()));
216  }
217  if (n == 0) {
218    ev->Error("*** first argument to `word' function must be greater than 0.");
219  }
220
221  const string&& text = args[1]->Eval(ev);
222  for (StringPiece tok : WordScanner(text)) {
223    n--;
224    if (n == 0) {
225      AppendString(tok, s);
226      break;
227    }
228  }
229}
230
231void WordlistFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
232  const string&& s_str = args[0]->Eval(ev);
233  int si = GetNumericValueForFunc(s_str);
234  if (si < 0) {
235    ev->Error(StringPrintf(
236        "*** non-numeric first argument to `wordlist' function: '%s'.",
237        s_str.c_str()));
238  }
239  if (si == 0) {
240    ev->Error(StringPrintf(
241        "*** invalid first argument to `wordlist' function: %s`",
242        s_str.c_str()));
243  }
244
245  const string&& e_str = args[1]->Eval(ev);
246  int ei = GetNumericValueForFunc(e_str);
247  if (ei < 0) {
248    ev->Error(StringPrintf(
249        "*** non-numeric second argument to `wordlist' function: '%s'.",
250        e_str.c_str()));
251  }
252
253  const string&& text = args[2]->Eval(ev);
254  int i = 0;
255  WordWriter ww(s);
256  for (StringPiece tok : WordScanner(text)) {
257    i++;
258    if (si <= i && i <= ei) {
259      ww.Write(tok);
260    }
261  }
262}
263
264void WordsFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
265  const string&& text = args[0]->Eval(ev);
266  WordScanner ws(text);
267  int n = 0;
268  for (auto iter = ws.begin(); iter != ws.end(); ++iter)
269    n++;
270  char buf[32];
271  sprintf(buf, "%d", n);
272  *s += buf;
273}
274
275void FirstwordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
276  const string&& text = args[0]->Eval(ev);
277  for (StringPiece tok : WordScanner(text)) {
278    AppendString(tok, s);
279    return;
280  }
281}
282
283void LastwordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
284  const string&& text = args[0]->Eval(ev);
285  StringPiece last;
286  for (StringPiece tok : WordScanner(text)) {
287    last = tok;
288  }
289  AppendString(last, s);
290}
291
292void JoinFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
293  const string&& list1 = args[0]->Eval(ev);
294  const string&& list2 = args[1]->Eval(ev);
295  WordScanner ws1(list1);
296  WordScanner ws2(list2);
297  WordWriter ww(s);
298  for (WordScanner::Iterator iter1 = ws1.begin(), iter2 = ws2.begin();
299       iter1 != ws1.end() && iter2 != ws2.end();
300       ++iter1, ++iter2) {
301    ww.Write(*iter1);
302    // Use |AppendString| not to append extra ' '.
303    AppendString(*iter2, s);
304  }
305}
306
307void WildcardFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
308  const string&& pat = args[0]->Eval(ev);
309  COLLECT_STATS("func wildcard time");
310  // Note GNU make does not delay the execution of $(wildcard) so we
311  // do not need to check avoid_io here.
312  WordWriter ww(s);
313  vector<string>* files;
314  for (StringPiece tok : WordScanner(pat)) {
315    ScopedTerminator st(tok);
316    Glob(tok.data(), &files);
317    sort(files->begin(), files->end());
318    for (const string& file : *files) {
319      ww.Write(file);
320    }
321  }
322}
323
324void DirFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
325  const string&& text = args[0]->Eval(ev);
326  WordWriter ww(s);
327  for (StringPiece tok : WordScanner(text)) {
328    ww.Write(Dirname(tok));
329    s->push_back('/');
330  }
331}
332
333void NotdirFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
334  const string&& text = args[0]->Eval(ev);
335  WordWriter ww(s);
336  for (StringPiece tok : WordScanner(text)) {
337    if (tok == "/") {
338      ww.Write(StringPiece(""));
339    } else {
340      ww.Write(Basename(tok));
341    }
342  }
343}
344
345void SuffixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
346  const string&& text = args[0]->Eval(ev);
347  WordWriter ww(s);
348  for (StringPiece tok : WordScanner(text)) {
349    StringPiece suf = GetExt(tok);
350    if (!suf.empty())
351      ww.Write(suf);
352  }
353}
354
355void BasenameFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
356  const string&& text = args[0]->Eval(ev);
357  WordWriter ww(s);
358  for (StringPiece tok : WordScanner(text)) {
359    ww.Write(StripExt(tok));
360  }
361}
362
363void AddsuffixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
364  const string&& suf = args[0]->Eval(ev);
365  const string&& text = args[1]->Eval(ev);
366  WordWriter ww(s);
367  for (StringPiece tok : WordScanner(text)) {
368    ww.Write(tok);
369    *s += suf;
370  }
371}
372
373void AddprefixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
374  const string&& pre = args[0]->Eval(ev);
375  const string&& text = args[1]->Eval(ev);
376  WordWriter ww(s);
377  for (StringPiece tok : WordScanner(text)) {
378    ww.Write(pre);
379    AppendString(tok, s);
380  }
381}
382
383void RealpathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
384  const string&& text = args[0]->Eval(ev);
385  if (ev->avoid_io()) {
386    *s += "$(";
387    string kati_binary;
388    GetExecutablePath(&kati_binary);
389    *s += kati_binary;
390    *s += " --realpath ";
391    *s += text;
392    *s += " 2> /dev/null)";
393    return;
394  }
395
396  WordWriter ww(s);
397  for (StringPiece tok : WordScanner(text)) {
398    ScopedTerminator st(tok);
399    char buf[PATH_MAX];
400    if (realpath(tok.data(), buf))
401      ww.Write(buf);
402  }
403}
404
405void AbspathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
406  const string&& text = args[0]->Eval(ev);
407  WordWriter ww(s);
408  string buf;
409  for (StringPiece tok : WordScanner(text)) {
410    AbsPath(tok, &buf);
411    ww.Write(buf);
412  }
413}
414
415void IfFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
416  const string&& cond = args[0]->Eval(ev);
417  if (cond.empty()) {
418    if (args.size() > 2)
419      args[2]->Eval(ev, s);
420  } else {
421    args[1]->Eval(ev, s);
422  }
423}
424
425void AndFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
426  string cond;
427  for (Value* a : args) {
428    cond = a->Eval(ev);
429    if (cond.empty())
430      return;
431  }
432  if (!cond.empty()) {
433    *s += cond;
434  }
435}
436
437void OrFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
438  for (Value* a : args) {
439    const string&& cond = a->Eval(ev);
440    if (!cond.empty()) {
441      *s += cond;
442      return;
443    }
444  }
445}
446
447void ValueFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
448  const string&& var_name = args[0]->Eval(ev);
449  Var* var = ev->LookupVar(Intern(var_name));
450  AppendString(var->String().as_string(), s);
451}
452
453void EvalFunc(const vector<Value*>& args, Evaluator* ev, string*) {
454  // TODO: eval leaks everything... for now.
455  //const string text = args[0]->Eval(ev);
456  string* text = new string;
457  args[0]->Eval(ev, text);
458  vector<Stmt*> stmts;
459  Parse(*text, ev->loc(), &stmts);
460  for (Stmt* stmt : stmts) {
461    LOG("%s", stmt->DebugString().c_str());
462    stmt->Eval(ev);
463    //delete stmt;
464  }
465}
466
467//#define TEST_FIND_EMULATOR
468
469// A hack for Android build. We need to evaluate things like $((3+4))
470// when we emit ninja file, because the result of such expressions
471// will be passed to other make functions.
472// TODO: Maybe we should introduce a helper binary which evaluate
473// make expressions at ninja-time.
474static bool HasNoIoInShellScript(const string& cmd) {
475  if (cmd.empty())
476    return true;
477  if (HasPrefix(cmd, "echo $((") && cmd[cmd.size()-1] == ')')
478    return true;
479  return false;
480}
481
482static void ShellFuncImpl(const string& shell, const string& cmd,
483                          string* s, FindCommand** fc) {
484  LOG("ShellFunc: %s", cmd.c_str());
485
486#ifdef TEST_FIND_EMULATOR
487  bool need_check = false;
488  string out2;
489#endif
490  if (FindEmulator::Get()) {
491    *fc = new FindCommand();
492    if ((*fc)->Parse(cmd)) {
493#ifdef TEST_FIND_EMULATOR
494      if (FindEmulator::Get()->HandleFind(cmd, **fc, &out2)) {
495        need_check = true;
496      }
497#else
498      if (FindEmulator::Get()->HandleFind(cmd, **fc, s)) {
499        return;
500      }
501#endif
502    }
503    delete *fc;
504    *fc = NULL;
505  }
506
507  COLLECT_STATS_WITH_SLOW_REPORT("func shell time", cmd.c_str());
508  RunCommand(shell, cmd, RedirectStderr::NONE, s);
509  FormatForCommandSubstitution(s);
510
511#ifdef TEST_FIND_EMULATOR
512  if (need_check) {
513    if (*s != out2) {
514      ERROR("FindEmulator is broken: %s\n%s\nvs\n%s",
515            cmd.c_str(), s->c_str(), out2.c_str());
516    }
517  }
518#endif
519}
520
521static vector<CommandResult*> g_command_results;
522
523bool ShouldStoreCommandResult(StringPiece cmd) {
524  if (HasWord(cmd, "date") || HasWord(cmd, "echo"))
525    return false;
526
527  Pattern pat(g_flags.ignore_dirty_pattern);
528  Pattern nopat(g_flags.no_ignore_dirty_pattern);
529  for (StringPiece tok : WordScanner(cmd)) {
530    if (pat.Match(tok) && !nopat.Match(tok)) {
531      return false;
532    }
533  }
534
535  return true;
536}
537
538void ShellFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
539  string cmd = args[0]->Eval(ev);
540  if (ev->avoid_io() && !HasNoIoInShellScript(cmd)) {
541    StripShellComment(&cmd);
542    *s += "$(";
543    *s += cmd;
544    *s += ")";
545    return;
546  }
547
548  const string&& shell = ev->EvalVar(kShellSym);
549
550  string out;
551  FindCommand* fc = NULL;
552  ShellFuncImpl(shell, cmd, &out, &fc);
553  if (ShouldStoreCommandResult(cmd)) {
554    CommandResult* cr = new CommandResult();
555    cr->cmd = cmd;
556    cr->find.reset(fc);
557    cr->result = out;
558    g_command_results.push_back(cr);
559  }
560  *s += out;
561}
562
563void CallFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
564  static const string tmpvar_names[] = {
565    "0", "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9"
566  };
567
568  const string&& func_name = args[0]->Eval(ev);
569  Var* func = ev->LookupVar(Intern(func_name));
570  vector<unique_ptr<SimpleVar>> av;
571  for (size_t i = 1; i < args.size(); i++) {
572    unique_ptr<SimpleVar> s(
573        new SimpleVar(args[i]->Eval(ev), VarOrigin::AUTOMATIC));
574    av.push_back(move(s));
575  }
576  vector<unique_ptr<ScopedVar>> sv;
577  for (size_t i = 1; ; i++) {
578    string s;
579    StringPiece tmpvar_name;
580    if (i < sizeof(tmpvar_names)/sizeof(tmpvar_names[0])) {
581      tmpvar_name = tmpvar_names[i];
582    } else {
583      s = StringPrintf("%d", i);
584      tmpvar_name = s;
585    }
586    if (i < args.size()) {
587      sv.emplace_back(new ScopedVar(ev->mutable_vars(),
588                                    Intern(tmpvar_name), av[i-1].get()));
589    } else {
590      // We need to blank further automatic vars
591      Var *v = ev->LookupVar(Intern(tmpvar_name));
592      if (!v->IsDefined()) break;
593      if (v->Origin() != VarOrigin::AUTOMATIC) break;
594
595      av.emplace_back(new SimpleVar("", VarOrigin::AUTOMATIC));
596      sv.emplace_back(new ScopedVar(ev->mutable_vars(),
597                                    Intern(tmpvar_name), av[i-1].get()));
598    }
599  }
600  func->Eval(ev, s);
601}
602
603void ForeachFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
604  const string&& varname = args[0]->Eval(ev);
605  const string&& list = args[1]->Eval(ev);
606  WordWriter ww(s);
607  for (StringPiece tok : WordScanner(list)) {
608    unique_ptr<SimpleVar> v(new SimpleVar(
609        tok.as_string(), VarOrigin::AUTOMATIC));
610    ScopedVar sv(ev->mutable_vars(), Intern(varname), v.get());
611    ww.MaybeAddWhitespace();
612    args[2]->Eval(ev, s);
613  }
614}
615
616void OriginFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
617  const string&& var_name = args[0]->Eval(ev);
618  Var* var = ev->LookupVar(Intern(var_name));
619  *s += GetOriginStr(var->Origin());
620}
621
622void FlavorFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
623  const string&& var_name = args[0]->Eval(ev);
624  Var* var = ev->LookupVar(Intern(var_name));
625  *s += var->Flavor();
626}
627
628void InfoFunc(const vector<Value*>& args, Evaluator* ev, string*) {
629  const string&& a = args[0]->Eval(ev);
630  if (ev->avoid_io()) {
631    ev->add_delayed_output_command(StringPrintf("echo -e \"%s\"", EchoEscape(a).c_str()));
632    return;
633  }
634  printf("%s\n", a.c_str());
635  fflush(stdout);
636}
637
638void WarningFunc(const vector<Value*>& args, Evaluator* ev, string*) {
639  const string&& a = args[0]->Eval(ev);
640  if (ev->avoid_io()) {
641    ev->add_delayed_output_command(
642        StringPrintf("echo -e \"%s:%d: %s\" 2>&1", LOCF(ev->loc()), EchoEscape(a).c_str()));
643    return;
644  }
645  printf("%s:%d: %s\n", LOCF(ev->loc()), a.c_str());
646  fflush(stdout);
647}
648
649void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string*) {
650  const string&& a = args[0]->Eval(ev);
651  if (ev->avoid_io()) {
652    ev->add_delayed_output_command(
653        StringPrintf("echo -e \"%s:%d: *** %s.\" 2>&1 && false",
654                     LOCF(ev->loc()), EchoEscape(a).c_str()));
655    return;
656  }
657  ev->Error(StringPrintf("*** %s.", a.c_str()));
658}
659
660FuncInfo g_func_infos[] = {
661  { "patsubst", &PatsubstFunc, 3, 3, false, false },
662  { "strip", &StripFunc, 1, 1, false, false },
663  { "subst", &SubstFunc, 3, 3, false, false },
664  { "findstring", &FindstringFunc, 2, 2, false, false },
665  { "filter", &FilterFunc, 2, 2, false, false },
666  { "filter-out", &FilterOutFunc, 2, 2, false, false },
667  { "sort", &SortFunc, 1, 1, false, false },
668  { "word", &WordFunc, 2, 2, false, false },
669  { "wordlist", &WordlistFunc, 3, 3, false, false },
670  { "words", &WordsFunc, 1, 1, false, false },
671  { "firstword", &FirstwordFunc, 1, 1, false, false },
672  { "lastword", &LastwordFunc, 1, 1, false, false },
673
674  { "join", &JoinFunc, 2, 2, false, false },
675  { "wildcard", &WildcardFunc, 1, 1, false, false },
676  { "dir", &DirFunc, 1, 1, false, false },
677  { "notdir", &NotdirFunc, 1, 1, false, false },
678  { "suffix", &SuffixFunc, 1, 1, false, false },
679  { "basename", &BasenameFunc, 1, 1, false, false },
680  { "addsuffix", &AddsuffixFunc, 2, 2, false, false },
681  { "addprefix", &AddprefixFunc, 2, 2, false, false },
682  { "realpath", &RealpathFunc, 1, 1, false, false },
683  { "abspath", &AbspathFunc, 1, 1, false, false },
684
685  { "if", &IfFunc, 3, 2, false, true },
686  { "and", &AndFunc, 0, 0, true, false },
687  { "or", &OrFunc, 0, 0, true, false },
688
689  { "value", &ValueFunc, 1, 1, false, false },
690  { "eval", &EvalFunc, 1, 1, false, false },
691  { "shell", &ShellFunc, 1, 1, false, false },
692  { "call", &CallFunc, 0, 0, false, false },
693  { "foreach", &ForeachFunc, 3, 3, false, false },
694
695  { "origin", &OriginFunc, 1, 1, false, false },
696  { "flavor", &FlavorFunc, 1, 1, false, false },
697
698  { "info", &InfoFunc, 1, 1, false, false },
699  { "warning", &WarningFunc, 1, 1, false, false },
700  { "error", &ErrorFunc, 1, 1, false, false },
701};
702
703unordered_map<StringPiece, FuncInfo*>* g_func_info_map;
704
705}  // namespace
706
707void InitFuncTable() {
708  g_func_info_map = new unordered_map<StringPiece, FuncInfo*>;
709  for (size_t i = 0; i < sizeof(g_func_infos) / sizeof(g_func_infos[0]); i++) {
710    FuncInfo* fi = &g_func_infos[i];
711    bool ok = g_func_info_map->emplace(fi->name, fi).second;
712    CHECK(ok);
713  }
714}
715
716void QuitFuncTable() {
717  delete g_func_info_map;
718}
719
720FuncInfo* GetFuncInfo(StringPiece name) {
721  auto found = g_func_info_map->find(name);
722  if (found == g_func_info_map->end())
723    return NULL;
724  return found->second;
725}
726
727const vector<CommandResult*>& GetShellCommandResults() {
728  return g_command_results;
729}
730