func.cc revision 41f30562cd4f906a1029a9b5c2991a38882d41a5
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 "ast.h"
31#include "eval.h"
32#include "fileutil.h"
33#include "find.h"
34#include "log.h"
35#include "parser.h"
36#include "stats.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.
45shared_ptr<string> StripShellComment(shared_ptr<string> cmd) {
46  if (cmd->find('#') == string::npos)
47    return cmd;
48
49  shared_ptr<string> res = make_shared<string>();
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 && *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  return res;
95}
96
97void PatsubstFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
98  shared_ptr<string> pat_str = args[0]->Eval(ev);
99  shared_ptr<string> repl = args[1]->Eval(ev);
100  shared_ptr<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  shared_ptr<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  shared_ptr<string> pat = args[0]->Eval(ev);
119  shared_ptr<string> repl = args[1]->Eval(ev);
120  shared_ptr<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  shared_ptr<string> find = args[0]->Eval(ev);
140  shared_ptr<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  shared_ptr<string> pat_buf = args[0]->Eval(ev);
147  shared_ptr<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  shared_ptr<string> pat_buf = args[0]->Eval(ev);
165  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<string> list1 = args[0]->Eval(ev);
294  shared_ptr<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  COLLECT_STATS("func wildcard time");
309  shared_ptr<string> pat = args[0]->Eval(ev);
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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<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  shared_ptr<string> suf = args[0]->Eval(ev);
365  shared_ptr<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  shared_ptr<string> pre = args[0]->Eval(ev);
375  shared_ptr<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  shared_ptr<string> text = args[0]->Eval(ev);
385  if (ev->avoid_io()) {
386    *s += "$(realpath ";
387    *s += *text;
388    *s += " 2> /dev/null)";
389    return;
390  }
391
392  WordWriter ww(s);
393  for (StringPiece tok : WordScanner(*text)) {
394    ScopedTerminator st(tok);
395    char buf[PATH_MAX];
396    if (realpath(tok.data(), buf))
397      *s += buf;
398  }
399}
400
401void AbspathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
402  shared_ptr<string> text = args[0]->Eval(ev);
403  WordWriter ww(s);
404  string buf;
405  for (StringPiece tok : WordScanner(*text)) {
406    AbsPath(tok, &buf);
407    ww.Write(buf);
408  }
409}
410
411void IfFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
412  shared_ptr<string> cond = args[0]->Eval(ev);
413  if (cond->empty()) {
414    if (args.size() > 2)
415      args[2]->Eval(ev, s);
416  } else {
417    args[1]->Eval(ev, s);
418  }
419}
420
421void AndFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
422  shared_ptr<string> cond;
423  for (Value* a : args) {
424    cond = a->Eval(ev);
425    if (cond->empty())
426      return;
427  }
428  if (cond.get()) {
429    *s += *cond;
430  }
431}
432
433void OrFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
434  for (Value* a : args) {
435    shared_ptr<string> cond = a->Eval(ev);
436    if (!cond->empty()) {
437      *s += *cond;
438      return;
439    }
440  }
441}
442
443void ValueFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
444  shared_ptr<string> var_name = args[0]->Eval(ev);
445  Var* var = ev->LookupVar(Intern(*var_name));
446  AppendString(var->String().as_string(), s);
447}
448
449void EvalFunc(const vector<Value*>& args, Evaluator* ev, string*) {
450  // TODO: eval leaks everything... for now.
451  //shared_ptr<string> text = args[0]->Eval(ev);
452  string* text = new string;
453  args[0]->Eval(ev, text);
454  vector<AST*> asts;
455  Parse(*text, ev->loc(), &asts);
456  for (AST* ast : asts) {
457    LOG("%s", ast->DebugString().c_str());
458    ast->Eval(ev);
459    //delete ast;
460  }
461}
462
463//#define TEST_FIND_EMULATOR
464
465// A hack for Android build. We need to evaluate things like $((3+4))
466// when we emit ninja file, because the result of such expressions
467// will be passed to other make functions.
468// TODO: Maybe we should introduce a helper binary which evaluate
469// make expressions at ninja-time.
470static bool HasNoIoInShellScript(const string& cmd) {
471  if (cmd.empty())
472    return true;
473  if (HasPrefix(cmd, "echo $((") && cmd[cmd.size()-1] == ')')
474    return true;
475  return false;
476}
477
478static void ShellFuncImpl(const string& shell, const string& cmd,
479                          string* s, FindCommand** fc) {
480  LOG("ShellFunc: %s", cmd.c_str());
481
482#ifdef TEST_FIND_EMULATOR
483  bool need_check = false;
484  string out2;
485#endif
486  if (FindEmulator::Get()) {
487    *fc = new FindCommand();
488    if ((*fc)->Parse(cmd)) {
489#ifdef TEST_FIND_EMULATOR
490      if (FindEmulator::Get()->HandleFind(cmd, **fc, &out2)) {
491        need_check = true;
492      }
493#else
494      if (FindEmulator::Get()->HandleFind(cmd, **fc, s)) {
495        return;
496      }
497#endif
498    }
499    delete *fc;
500    *fc = NULL;
501  }
502
503  COLLECT_STATS_WITH_SLOW_REPORT("func shell time", cmd.c_str());
504  RunCommand(shell, cmd, RedirectStderr::NONE, s);
505  FormatForCommandSubstitution(s);
506
507#ifdef TEST_FIND_EMULATOR
508  if (need_check) {
509    if (*s != out2) {
510      ERROR("FindEmulator is broken: %s\n%s\nvs\n%s",
511            cmd.c_str(), s->c_str(), out2.c_str());
512    }
513  }
514#endif
515}
516
517static vector<FileListCommand*> g_file_list_commands;
518
519void ShellFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
520  shared_ptr<string> cmd = args[0]->Eval(ev);
521  if (ev->avoid_io() && !HasNoIoInShellScript(*cmd)) {
522    *s += "$(";
523    *s += *StripShellComment(cmd);
524    *s += ")";
525    return;
526  }
527
528  shared_ptr<string> shell = ev->EvalVar(kShellSym);
529  const bool is_file_list_command =
530      ((*shell == "/bin/sh" || *shell == "/bin/bash") &&
531       (HasWord(*cmd, "find") || HasWord(*cmd, "ls") ||
532        // For Android.
533        cmd->find("/findleaves.py ") != string::npos));
534
535  string out;
536  FindCommand* fc = NULL;
537  ShellFuncImpl(*shell, *cmd, &out, &fc);
538  if (is_file_list_command) {
539    FileListCommand* flc = new FileListCommand();
540    flc->cmd = *cmd;
541    //fprintf(stderr, "file list command: %s %p\n", cmd->c_str(), fc);
542    flc->find.reset(fc);
543    flc->result = out;
544    g_file_list_commands.push_back(flc);
545  }
546  *s += out;
547}
548
549void CallFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
550  static const string tmpvar_names[] = {
551    "0", "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9"
552  };
553
554  shared_ptr<string> func_name = args[0]->Eval(ev);
555  Var* func = ev->LookupVar(Intern(*func_name));
556  vector<unique_ptr<SimpleVar>> av;
557  for (size_t i = 1; i < args.size(); i++) {
558    unique_ptr<SimpleVar> s(
559        new SimpleVar(args[i]->Eval(ev), VarOrigin::AUTOMATIC));
560    av.push_back(move(s));
561  }
562  vector<unique_ptr<ScopedVar>> sv;
563  for (size_t i = 1; i < args.size(); i++) {
564    string s;
565    StringPiece tmpvar_name;
566    if (i < sizeof(tmpvar_names)/sizeof(tmpvar_names[0])) {
567      tmpvar_name = tmpvar_names[i];
568    } else {
569      s = StringPrintf("%d", i);
570      tmpvar_name = s;
571    }
572    sv.push_back(move(unique_ptr<ScopedVar>(
573        new ScopedVar(ev->mutable_vars(),
574                      Intern(tmpvar_name), av[i-1].get()))));
575  }
576  func->Eval(ev, s);
577}
578
579void ForeachFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
580  shared_ptr<string> varname = args[0]->Eval(ev);
581  shared_ptr<string> list = args[1]->Eval(ev);
582  WordWriter ww(s);
583  for (StringPiece tok : WordScanner(*list)) {
584    unique_ptr<SimpleVar> v(new SimpleVar(
585        make_shared<string>(tok.data(), tok.size()), VarOrigin::AUTOMATIC));
586    ScopedVar sv(ev->mutable_vars(), Intern(*varname), v.get());
587    ww.MaybeAddWhitespace();
588    args[2]->Eval(ev, s);
589  }
590}
591
592void OriginFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
593  shared_ptr<string> var_name = args[0]->Eval(ev);
594  Var* var = ev->LookupVar(Intern(*var_name));
595  *s += GetOriginStr(var->Origin());
596}
597
598void FlavorFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
599  shared_ptr<string> var_name = args[0]->Eval(ev);
600  Var* var = ev->LookupVar(Intern(*var_name));
601  *s += var->Flavor();
602}
603
604void InfoFunc(const vector<Value*>& args, Evaluator* ev, string*) {
605  shared_ptr<string> a = args[0]->Eval(ev);
606  if (ev->avoid_io()) {
607    ev->add_delayed_output_command(StringPrintf("echo '%s'", a->c_str()));
608    return;
609  }
610  printf("%s\n", a->c_str());
611  fflush(stdout);
612}
613
614void WarningFunc(const vector<Value*>& args, Evaluator* ev, string*) {
615  shared_ptr<string> a = args[0]->Eval(ev);
616  if (ev->avoid_io()) {
617    ev->add_delayed_output_command(
618        StringPrintf("echo '%s:%d: %s' 2>&1", LOCF(ev->loc()), a->c_str()));
619    return;
620  }
621  printf("%s:%d: %s\n", LOCF(ev->loc()), a->c_str());
622  fflush(stdout);
623}
624
625void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string*) {
626  shared_ptr<string> a = args[0]->Eval(ev);
627  if (ev->avoid_io()) {
628    ev->add_delayed_output_command(
629        StringPrintf("echo '%s:%d: *** %s.' 2>&1 && false",
630                     LOCF(ev->loc()), a->c_str()));
631    return;
632  }
633  ev->Error(StringPrintf("*** %s.", a->c_str()));
634}
635
636FuncInfo g_func_infos[] = {
637  { "patsubst", &PatsubstFunc, 3, 3, false, false },
638  { "strip", &StripFunc, 1, 1, false, false },
639  { "subst", &SubstFunc, 3, 3, false, false },
640  { "findstring", &FindstringFunc, 2, 2, false, false },
641  { "filter", &FilterFunc, 2, 2, false, false },
642  { "filter-out", &FilterOutFunc, 2, 2, false, false },
643  { "sort", &SortFunc, 1, 1, false, false },
644  { "word", &WordFunc, 2, 2, false, false },
645  { "wordlist", &WordlistFunc, 3, 3, false, false },
646  { "words", &WordsFunc, 1, 1, false, false },
647  { "firstword", &FirstwordFunc, 1, 1, false, false },
648  { "lastword", &LastwordFunc, 1, 1, false, false },
649
650  { "join", &JoinFunc, 2, 2, false, false },
651  { "wildcard", &WildcardFunc, 1, 1, false, false },
652  { "dir", &DirFunc, 1, 1, false, false },
653  { "notdir", &NotdirFunc, 1, 1, false, false },
654  { "suffix", &SuffixFunc, 1, 1, false, false },
655  { "basename", &BasenameFunc, 1, 1, false, false },
656  { "addsuffix", &AddsuffixFunc, 2, 2, false, false },
657  { "addprefix", &AddprefixFunc, 2, 2, false, false },
658  { "realpath", &RealpathFunc, 1, 1, false, false },
659  { "abspath", &AbspathFunc, 1, 1, false, false },
660
661  { "if", &IfFunc, 3, 2, false, true },
662  { "and", &AndFunc, 0, 0, true, false },
663  { "or", &OrFunc, 0, 0, true, false },
664
665  { "value", &ValueFunc, 1, 1, false, false },
666  { "eval", &EvalFunc, 1, 1, false, false },
667  { "shell", &ShellFunc, 1, 1, false, false },
668  { "call", &CallFunc, 0, 0, false, false },
669  { "foreach", &ForeachFunc, 3, 3, false, false },
670
671  { "origin", &OriginFunc, 1, 1, false, false },
672  { "flavor", &FlavorFunc, 1, 1, false, false },
673
674  { "info", &InfoFunc, 1, 1, false, false },
675  { "warning", &WarningFunc, 1, 1, false, false },
676  { "error", &ErrorFunc, 1, 1, false, false },
677};
678
679unordered_map<StringPiece, FuncInfo*>* g_func_info_map;
680
681}  // namespace
682
683void InitFuncTable() {
684  g_func_info_map = new unordered_map<StringPiece, FuncInfo*>;
685  for (size_t i = 0; i < sizeof(g_func_infos) / sizeof(g_func_infos[0]); i++) {
686    FuncInfo* fi = &g_func_infos[i];
687    bool ok = g_func_info_map->emplace(fi->name, fi).second;
688    CHECK(ok);
689  }
690}
691
692void QuitFuncTable() {
693  delete g_func_info_map;
694}
695
696FuncInfo* GetFuncInfo(StringPiece name) {
697  auto found = g_func_info_map->find(name);
698  if (found == g_func_info_map->end())
699    return NULL;
700  return found->second;
701}
702
703const vector<FileListCommand*>& GetFileListCommmands() {
704  return g_file_list_commands;
705}
706