1 // Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2 //
3 // This program is made available under the GNU GPL version 2.0 or
4 // greater. See the accompanying file COPYING for details.
5 //
6 // This program is distributed WITHOUT ANY WARRANTY; without even the
7 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8 // PURPOSE.
9 
10 #include "base.hh"
11 #include "selectors.hh"
12 
13 #include "sanity.hh"
14 #include "constants.hh"
15 #include "database.hh"
16 #include "app_state.hh"
17 #include "project.hh"
18 #include "revision.hh"
19 #include "globish.hh"
20 #include "cmd.hh"
21 #include "work.hh"
22 #include "transforms.hh"
23 #include "roster.hh"
24 #include "vector.hh"
25 #include "vocab_cast.hh"
26 
27 #include <algorithm>
28 #include <boost/shared_ptr.hpp>
29 #include <boost/tokenizer.hpp>
30 
31 using std::make_pair;
32 using std::pair;
33 using std::set;
34 using std::string;
35 using std::vector;
36 using std::set_intersection;
37 using std::inserter;
38 
39 using boost::shared_ptr;
40 
41 void
diagnose_ambiguous_expansion(options const & opts,lua_hooks & lua,project_t & project,string const & str,set<revision_id> const & completions)42 diagnose_ambiguous_expansion(options const & opts, lua_hooks & lua,
43                              project_t & project,
44                              string const & str,
45                              set<revision_id> const & completions)
46 {
47   if (completions.size() <= 1)
48     return;
49 
50   string err = (F("selection '%s' has multiple ambiguous expansions:")
51                 % str).str();
52   for (set<revision_id>::const_iterator i = completions.begin();
53        i != completions.end(); ++i)
54     err += ("\n" + describe_revision(opts, lua, project, *i));
55 
56   E(false, origin::user, i18n_format(err));
57 }
58 
59 class selector
60 {
61 public:
62   static shared_ptr<selector> create(options const & opts,
63                                      lua_hooks & lua,
64                                      project_t & project,
65                                      string const & orig);
66   static shared_ptr<selector> create_simple_selector(options const & opts,
67                                                      lua_hooks & lua,
68                                                      project_t & project,
69                                                      string const & orig);
70   virtual set<revision_id> complete(project_t & project) = 0;
71   virtual ~selector();
72 };
~selector()73 selector::~selector() { }
74 
75 class author_selector : public selector
76 {
77   string value;
78 public:
author_selector(string const & arg)79   author_selector(string const & arg) : value(arg) {}
complete(project_t & project)80   virtual set<revision_id> complete(project_t & project)
81   {
82     set<revision_id> ret;
83     project.db.select_cert(author_cert_name(), value, ret);
84     return ret;
85   }
86 };
87 class key_selector : public selector
88 {
89   key_identity_info identity;
90 public:
key_selector(string const & arg,lua_hooks & lua,project_t & project)91   key_selector(string const & arg, lua_hooks & lua, project_t & project)
92   {
93     E(!arg.empty(), origin::user,
94       F("the key selector k: must not be empty"));
95 
96     project.get_key_identity(lua,
97                              external_key_name(arg, origin::user),
98                              identity);
99   }
complete(project_t & project)100   virtual set<revision_id> complete(project_t & project)
101   {
102     set<revision_id> ret;
103     project.db.select_key(identity.id, ret);
104     return ret;
105   }
106 };
107 class branch_selector : public selector
108 {
109   string value;
110 public:
branch_selector(string const & arg,options const & opts)111   branch_selector(string const & arg, options const & opts) : value(arg)
112   {
113     if (value.empty())
114       {
115         workspace::require_workspace(F("the empty branch selector b: refers to the current branch"));
116         value = opts.branch();
117       }
118   }
complete(project_t & project)119   virtual set<revision_id> complete(project_t & project)
120   {
121     set<revision_id> ret;
122     project.db.select_cert(branch_cert_name(), value, ret);
123     return ret;
124   }
125 };
126 class cert_selector : public selector
127 {
128   string value;
129 public:
cert_selector(string const & arg)130   cert_selector(string const & arg) : value(arg)
131   {
132     E(!value.empty(), origin::user,
133       F("the cert selector c: may not be empty"));
134   }
complete(project_t & project)135   virtual set<revision_id> complete(project_t & project)
136   {
137     set<revision_id> ret;
138     size_t equals = value.find("=");
139     if (equals == string::npos)
140       project.db.select_cert(value, ret);
141     else
142       project.db.select_cert(value.substr(0, equals), value.substr(equals + 1), ret);
143     return ret;
144   }
145 };
preprocess_date_for_selector(string sel,lua_hooks & lua,bool equals)146 string preprocess_date_for_selector(string sel, lua_hooks & lua, bool equals)
147 {
148   string tmp;
149   if (lua.hook_exists("expand_date"))
150     {
151       E(lua.hook_expand_date(sel, tmp), origin::user,
152         F("selector '%s' is not a valid date") % sel);
153     }
154   else
155     {
156       tmp = sel;
157     }
158   // if we still have a too short datetime string, expand it with
159   // default values, but only if the type is earlier or later;
160   // for searching a specific date cert this makes no sense
161   // FIXME: this is highly speculative if expand_date wasn't called
162   // beforehand - tmp could be _anything_ but a partial date string
163   if (tmp.size()<8 && !equals)
164     tmp += "-01T00:00:00";
165   else if (tmp.size()<11 && !equals)
166     tmp += "T00:00:00";
167   E(tmp.size()==19 || equals, origin::user,
168     F("selector '%s' is not a valid date (internally completed to '%s')") % sel % tmp);
169 
170   if (sel != tmp)
171     {
172       P(F("expanded date '%s' to UTC -> '%s'") % sel % tmp);
173       sel = tmp;
174     }
175   if (equals && sel.size() < 19)
176     sel = string("*") + sel + "*"; // to be GLOBbed later
177   return sel;
178 }
179 class date_selector : public selector
180 {
181   string value;
182 public:
date_selector(string const & arg,lua_hooks & lua)183   date_selector(string const & arg, lua_hooks & lua)
184     : value(preprocess_date_for_selector(arg, lua, true)) {}
complete(project_t & project)185   virtual set<revision_id> complete(project_t & project)
186   {
187     set<revision_id> ret;
188     project.db.select_date(value, "GLOB", ret);
189     return ret;
190   }
191 };
192 class earlier_than_selector : public selector
193 {
194   string value;
195 public:
earlier_than_selector(string const & arg,lua_hooks & lua)196   earlier_than_selector(string const & arg, lua_hooks & lua)
197     : value(preprocess_date_for_selector(arg, lua, false)) {}
complete(project_t & project)198   virtual set<revision_id> complete(project_t & project)
199   {
200     set<revision_id> ret;
201     project.db.select_date(value, "<=", ret);
202     return ret;
203   }
204 };
205 class head_selector : public selector
206 {
207   string value;
208   bool ignore_suspend;
209 public:
head_selector(string const & arg,options const & opts)210   head_selector(string const & arg, options const & opts)
211     : value(arg)
212   {
213     if (value.empty())
214       {
215         workspace::require_workspace(F("the empty head selector h: refers to "
216                                        "the head of the current branch"));
217         value = opts.branch();
218       }
219     ignore_suspend = opts.ignore_suspend_certs;
220   }
complete(project_t & project)221   virtual set<revision_id> complete(project_t & project)
222   {
223     set<revision_id> ret;
224 
225     set<branch_name> branch_names;
226     project.get_branch_list(globish(value, origin::user), branch_names);
227 
228     L(FL("found %d matching branches") % branch_names.size());
229 
230     // for each branch name, get the branch heads
231     for (set<branch_name>::const_iterator bn = branch_names.begin();
232          bn != branch_names.end(); bn++)
233       {
234         set<revision_id> branch_heads;
235         project.get_branch_heads(*bn, branch_heads, ignore_suspend);
236         ret.insert(branch_heads.begin(), branch_heads.end());
237         L(FL("after get_branch_heads for %s, heads has %d entries")
238           % (*bn) % ret.size());
239       }
240 
241     return ret;
242   }
243 };
244 class ident_selector : public selector
245 {
246   string value;
247 public:
ident_selector(string const & arg)248   ident_selector(string const & arg) : value(arg) {}
complete(project_t & project)249   virtual set<revision_id> complete(project_t & project)
250   {
251     set<revision_id> ret;
252     project.db.complete(value, ret);
253     return ret;
254   }
is_full_length() const255   bool is_full_length() const
256   {
257     return value.size() == constants::idlen;
258   }
get_assuming_full_length() const259   revision_id get_assuming_full_length() const
260   {
261     return decode_hexenc_as<revision_id>(value, origin::user);
262   }
263 };
264 class later_than_selector : public selector
265 {
266   string value;
267 public:
later_than_selector(string const & arg,lua_hooks & lua)268   later_than_selector(string const & arg, lua_hooks & lua)
269     : value(preprocess_date_for_selector(arg, lua, false)) {}
complete(project_t & project)270   virtual set<revision_id> complete(project_t & project)
271   {
272     set<revision_id> ret;
273     project.db.select_date(value, ">", ret);
274     return ret;
275   }
276 };
277 class message_selector : public selector
278 {
279   string value;
280 public:
message_selector(string const & arg)281   message_selector(string const & arg) : value(arg) {}
complete(project_t & project)282   virtual set<revision_id> complete(project_t & project)
283   {
284     set<revision_id> changelogs, comments;
285     project.db.select_cert(changelog_cert_name(), value, changelogs);
286     project.db.select_cert(comment_cert_name(), value, comments);
287 
288     changelogs.insert(comments.begin(), comments.end());
289     return changelogs;
290   }
291 };
292 class parent_selector : public selector
293 {
294   string value;
295 public:
parent_selector(string const & arg,options const & opts,lua_hooks & lua,project_t & project)296   parent_selector(string const & arg,
297                   options const & opts,
298                   lua_hooks & lua,
299                   project_t & project)
300     : value(arg)
301   {
302     if (value.empty())
303       {
304         workspace work(lua, F("the empty parent selector p: refers to "
305                               "the base revision of the workspace"));
306 
307         parent_map parents;
308         set<revision_id> parent_ids;
309 
310         work.get_parent_rosters(project.db, parents);
311 
312         for (parent_map::const_iterator i = parents.begin();
313              i != parents.end(); ++i)
314           {
315             parent_ids.insert(i->first);
316           }
317 
318         diagnose_ambiguous_expansion(opts, lua, project, "p:", parent_ids);
319         value = encode_hexenc((* parent_ids.begin()).inner()(),
320                               origin::internal);
321 
322       }
323   }
complete(project_t & project)324   virtual set<revision_id> complete(project_t & project)
325   {
326     set<revision_id> ret;
327     project.db.select_parent(value, ret);
328     return ret;
329   }
330 };
331 class tag_selector : public selector
332 {
333   string value;
334 public:
tag_selector(string const & arg)335   tag_selector(string const & arg) : value(arg) {}
complete(project_t & project)336   virtual set<revision_id> complete(project_t & project)
337   {
338     set<revision_id> ret;
339     project.db.select_cert(tag_cert_name(), value, ret);
340     return ret;
341   }
342 };
343 class update_selector : public selector
344 {
345   string value;
346 public:
update_selector(string const & arg,lua_hooks & lua)347   update_selector(string const & arg, lua_hooks & lua)
348   {
349     E(arg.empty(), origin::user,
350       F("no value is allowed with the update selector u:"));
351 
352     workspace work(lua, F("the update selector u: refers to the "
353                           "revision before the last update in the "
354                           "workspace"));
355     revision_id update_id;
356     work.get_update_id(update_id);
357     value = encode_hexenc(update_id.inner()(), origin::internal);
358   }
complete(project_t & project)359   virtual set<revision_id> complete(project_t & project)
360   {
361     set<revision_id> ret;
362     project.db.complete(value, ret);
363     return ret;
364   }
365 };
366 class working_base_selector : public selector
367 {
368   set<revision_id> ret;
369 public:
working_base_selector(string const & arg,project_t & project,lua_hooks & lua)370   working_base_selector(string const & arg, project_t & project, lua_hooks & lua)
371   {
372     E(arg.empty(), origin::user,
373       F("no value is allowed with the base revision selector w:"));
374 
375     workspace work(lua, F("the selector w: returns the "
376                           "base revision(s) of the workspace"));
377     parent_map parents;
378     work.get_parent_rosters(project.db, parents);
379 
380     for (parent_map::const_iterator i = parents.begin();
381          i != parents.end(); ++i)
382       {
383         ret.insert(i->first);
384       }
385   }
complete(project_t & project)386   virtual set<revision_id> complete(project_t & project)
387   {
388     return ret;
389   }
390 };
391 
392 class unknown_selector : public selector
393 {
394   string value;
395 public:
unknown_selector(string const & arg)396   unknown_selector(string const & arg) : value(arg) {}
complete(project_t & project)397   virtual set<revision_id> complete(project_t & project)
398   {
399     set<revision_id> ret;
400     project.db.select_author_tag_or_branch(value, ret);
401     return ret;
402   }
403 };
404 
405 class or_selector : public selector
406 {
407   vector<shared_ptr<selector> > members;
408 public:
add(shared_ptr<selector> s)409   void add(shared_ptr<selector> s)
410   {
411     members.push_back(s);
412   }
complete(project_t & project)413   virtual set<revision_id> complete(project_t & project)
414   {
415     set<revision_id> ret;
416     for (vector<shared_ptr<selector> >::const_iterator i = members.begin();
417          i != members.end(); ++i)
418       {
419         set<revision_id> current = (*i)->complete(project);
420         ret.insert(current.begin(), current.end());
421       }
422     return ret;
423   }
424 };
425 class and_selector : public selector
426 {
427   vector<shared_ptr<selector> > members;
428 public:
add(shared_ptr<selector> s)429   void add(shared_ptr<selector> s)
430   {
431     members.push_back(s);
432   }
complete(project_t & project)433   virtual set<revision_id> complete(project_t & project)
434   {
435     set<revision_id> ret;
436     bool first = true;
437     for (vector<shared_ptr<selector> >::const_iterator i = members.begin();
438          i != members.end(); ++i)
439       {
440         set<revision_id> current = (*i)->complete(project);
441         if (first)
442           {
443             first = false;
444             ret = current;
445           }
446         else
447           {
448             set<revision_id> intersection;
449             set_intersection(ret.begin(), ret.end(),
450                              current.begin(), current.end(),
451                              inserter(intersection, intersection.end()));
452             ret = intersection;
453           }
454       }
455     return ret;
456   }
457 };
458 class nested_selector : public selector
459 {
460   shared_ptr<selector> s;
461 public:
nested_selector(shared_ptr<selector> s)462   nested_selector(shared_ptr<selector> s) : s(s) {}
complete(project_t & project)463   virtual set<revision_id> complete(project_t & project)
464   {
465     return s->complete(project);
466   }
467 };
468 
get_ancestors(project_t const & project,set<revision_id> frontier)469 set<revision_id> get_ancestors(project_t const & project,
470                                set<revision_id> frontier)
471 {
472   set<revision_id> ret;
473   while (!frontier.empty())
474     {
475       revision_id revid = *frontier.begin();
476       frontier.erase(frontier.begin());
477       set<revision_id> p;
478       project.db.get_revision_parents(revid, p);
479       for (set<revision_id>::const_iterator i = p.begin();
480            i != p.end(); ++i)
481         {
482           if (null_id(*i))
483             continue;
484           pair<set<revision_id>::iterator, bool> x = ret.insert(*i);
485           if (x.second)
486             frontier.insert(*i);
487         }
488     }
489   return ret;
490 }
491 
492 static void
diagnose_wrong_arg_count(string const & func,int expected,int actual)493 diagnose_wrong_arg_count(string const & func, int expected, int actual)
494 {
495   E(expected == actual, origin::user,
496     FP("the '%s' function takes %d argument, not %d",
497        "the '%s' function takes %d arguments, not %d",
498        expected)
499       % func % expected % actual);
500 }
501 
502 class fn_selector : public selector
503 {
504   string name;
505   vector<shared_ptr<selector> > args;
506 public:
fn_selector(string const & fn_name)507   fn_selector(string const & fn_name) : name(fn_name) {}
add(shared_ptr<selector> s)508   void add(shared_ptr<selector> s)
509   {
510     args.push_back(s);
511   }
complete(project_t & project)512   virtual set<revision_id> complete(project_t & project)
513   {
514     if (name == "difference")
515       {
516         diagnose_wrong_arg_count("difference", 2, args.size());
517         set<revision_id> lhs = args[0]->complete(project);
518         set<revision_id> rhs = args[1]->complete(project);
519 
520         set<revision_id> ret;
521         set_difference(lhs.begin(), lhs.end(),
522                        rhs.begin(), rhs.end(),
523                        inserter(ret, ret.end()));
524         return ret;
525       }
526     else if (name == "not")
527       {
528         diagnose_wrong_arg_count("not", 1, args.size());
529         set<revision_id> lhs;
530         set<revision_id> rhs = args[0]->complete(project);
531 
532         project.db.get_revision_ids(lhs);
533         set<revision_id> ret;
534         set_difference(lhs.begin(), lhs.end(),
535                        rhs.begin(), rhs.end(),
536                        inserter(ret, ret.end()));
537         return ret;
538       }
539     else if (name == "lca")
540       {
541         diagnose_wrong_arg_count("lca", 2, args.size());
542         set<revision_id> lhs_heads = args[0]->complete(project);
543         set<revision_id> rhs_heads = args[1]->complete(project);
544         set<revision_id> lhs = get_ancestors(project, lhs_heads);
545         set<revision_id> rhs = get_ancestors(project, rhs_heads);
546         lhs.insert(lhs_heads.begin(), lhs_heads.end());
547         rhs.insert(rhs_heads.begin(), rhs_heads.end());
548         set<revision_id> common;
549         set_intersection(lhs.begin(), lhs.end(),
550                          rhs.begin(), rhs.end(),
551                          inserter(common, common.end()));
552         erase_ancestors(project.db, common);
553         return common;
554       }
555     else if (name == "max")
556       {
557         diagnose_wrong_arg_count("max", 1, args.size());
558         set<revision_id> ret = args[0]->complete(project);
559         erase_ancestors(project.db, ret);
560         return ret;
561       }
562     else if (name == "min")
563       {
564         diagnose_wrong_arg_count("min", 1, args.size());
565         set<revision_id> ret = args[0]->complete(project);
566         erase_descendants(project.db, ret);
567         return ret;
568       }
569     else if (name == "ancestors")
570       {
571         diagnose_wrong_arg_count("ancestors", 1, args.size());
572         return get_ancestors(project, args[0]->complete(project));
573       }
574     else if (name == "descendants")
575       {
576         diagnose_wrong_arg_count("descendants", 1, args.size());
577         set<revision_id> frontier = args[0]->complete(project);
578         set<revision_id> ret;
579         while (!frontier.empty())
580           {
581             revision_id revid = *frontier.begin();
582             frontier.erase(frontier.begin());
583             set<revision_id> c;
584             project.db.get_revision_children(revid, c);
585             for (set<revision_id>::const_iterator i = c.begin();
586                  i != c.end(); ++i)
587               {
588                 if (null_id(*i))
589                   continue;
590                 pair<set<revision_id>::iterator, bool> x = ret.insert(*i);
591                 if (x.second)
592                   frontier.insert(*i);
593               }
594           }
595         return ret;
596       }
597     else if (name == "parents")
598       {
599         diagnose_wrong_arg_count("parents", 1, args.size());
600         set<revision_id> ret;
601         set<revision_id> tmp = args[0]->complete(project);
602         for (set<revision_id>::const_iterator i = tmp.begin();
603              i != tmp.end(); ++i)
604           {
605             set<revision_id> p;
606             project.db.get_revision_parents(*i, p);
607             ret.insert(p.begin(), p.end());
608           }
609         ret.erase(revision_id());
610         return ret;
611       }
612     else if (name == "children")
613       {
614         diagnose_wrong_arg_count("children", 1, args.size());
615         set<revision_id> ret;
616         set<revision_id> tmp = args[0]->complete(project);
617         for (set<revision_id>::const_iterator i = tmp.begin();
618              i != tmp.end(); ++i)
619           {
620             set<revision_id> c;
621             project.db.get_revision_children(*i, c);
622             ret.insert(c.begin(), c.end());
623           }
624         ret.erase(revision_id());
625         return ret;
626       }
627     else if (name == "pick")
628       {
629         diagnose_wrong_arg_count("pick", 1, args.size());
630         set<revision_id> tmp = args[0]->complete(project);
631         set<revision_id> ret;
632         if (!tmp.empty())
633           ret.insert(*tmp.begin());
634         return ret;
635       }
636     else
637       {
638         E(false, origin::user,
639           F("unknown selection function '%s'") % name);
640       }
641   }
642 };
643 
644 struct parse_item
645 {
646   shared_ptr<selector> sel;
647   string str;
parse_itemparse_item648   explicit parse_item(shared_ptr<selector> const & s) : sel(s) { }
parse_itemparse_item649   explicit parse_item(string const & s) : str(s) { }
650 };
651 
652 shared_ptr<selector>
create_simple_selector(options const & opts,lua_hooks & lua,project_t & project,string const & orig)653 selector::create_simple_selector(options const & opts,
654                                  lua_hooks & lua,
655                                  project_t & project,
656                                  string const & orig)
657 {
658   string sel = orig;
659   if (sel.find_first_not_of(constants::legal_id_bytes) == string::npos
660       && sel.size() == constants::idlen)
661     return shared_ptr<selector>(new ident_selector(sel));
662 
663   if (sel.size() < 2 || sel[1] != ':')
664     {
665       string tmp;
666       if (!lua.hook_expand_selector(sel, tmp))
667         {
668           L(FL("expansion of selector '%s' failed") % sel);
669         }
670       else
671         {
672           P(F("expanded selector '%s' -> '%s'") % sel % tmp);
673           sel = tmp;
674         }
675     }
676   if (sel.size() < 2 || sel[1] != ':')
677     return shared_ptr<selector>(new unknown_selector(sel));
678   char sel_type = sel[0];
679   sel.erase(0,2);
680   switch (sel_type)
681     {
682     case 'a':
683       return shared_ptr<selector>(new author_selector(sel));
684     case 'b':
685       return shared_ptr<selector>(new branch_selector(sel, opts));
686     case 'c':
687       return shared_ptr<selector>(new cert_selector(sel));
688     case 'd':
689       return shared_ptr<selector>(new date_selector(sel, lua));
690     case 'e':
691       return shared_ptr<selector>(new earlier_than_selector(sel, lua));
692     case 'h':
693       return shared_ptr<selector>(new head_selector(sel, opts));
694     case 'i':
695       return shared_ptr<selector>(new ident_selector(sel));
696     case 'k':
697       return shared_ptr<selector>(new key_selector(sel, lua, project));
698     case 'l':
699       return shared_ptr<selector>(new later_than_selector(sel, lua));
700     case 'm':
701       return shared_ptr<selector>(new message_selector(sel));
702     case 'p':
703       return shared_ptr<selector>(new parent_selector(sel, opts, lua, project));
704     case 't':
705       return shared_ptr<selector>(new tag_selector(sel));
706     case 'u':
707       return shared_ptr<selector>(new update_selector(sel, lua));
708     case 'w':
709       return shared_ptr<selector>(new working_base_selector(sel, project, lua));
710     default:
711       E(false, origin::user, F("unknown selector type: %c") % sel_type);
712     }
713 }
714 
create(options const & opts,lua_hooks & lua,project_t & project,string const & orig)715 shared_ptr<selector> selector::create(options const & opts,
716                                       lua_hooks & lua,
717                                       project_t & project,
718                                       string const & orig)
719 {
720   // I would try to use lex/yacc for this, but they kinda look like a mess
721   // with lots of global variables and icky macros and such in the output.
722   // Using bisonc++ with flex in c++ mode might be better, except that
723   // bisonc++ is GPLv3 *without* (as far as I can see) an exception for use
724   // of the parser skeleton as included in the output.
725   string const special_chars("();\\/|");
726   boost::char_separator<char> splitter("", special_chars.c_str());
727   typedef boost::tokenizer<boost::char_separator<char> > tokenizer_t;
728   tokenizer_t tokenizer(orig, splitter);
729   vector<string> splitted;
730   L(FL("tokenizing selector '%s'") % orig);
731   bool dont_advance = false;
732   for (tokenizer_t::const_iterator iter = tokenizer.begin();
733        iter != tokenizer.end(); dont_advance || (++iter, true))
734     {
735       dont_advance = false;
736       string const & val = *iter;
737       if (val != "\\")
738         splitted.push_back(val);
739       else
740         {
741           if (splitted.empty())
742             splitted.push_back(string());
743 
744           ++iter;
745           E(iter != tokenizer.end(), origin::user,
746             F("selector '%s' is invalid, it ends with the escape character '\\'")
747             % orig);
748           string const & val2 = *iter;
749           I(!val2.empty());
750           E(special_chars.find(val2) != string::npos, origin::user,
751             F("selector '%s' is invalid, it contains an unknown escape sequence '%s%s'")
752             % val % '\\' % val2.substr(0,1));
753           splitted.back().append(val2);
754 
755           ++iter;
756           if (iter != tokenizer.end())
757             {
758               string const & val3 = *iter;
759               if (val3.size() != 1 || special_chars.find(val3) == string::npos)
760                 splitted.back().append(val3);
761               else
762                 dont_advance = true;
763             }
764         }
765     }
766   if (splitted.empty())
767     splitted.push_back(string());
768   for (vector<string>::const_iterator i = splitted.begin();
769        i != splitted.end(); ++i)
770     {
771       L(FL("tokens: '%s'") % *i);
772     }
773 
774   vector<parse_item> items;
775   size_t tok_num = 0;
776   for (vector<string>::const_iterator tok = splitted.begin();
777        tok != splitted.end(); ++tok)
778     {
779       L(FL("Processing token number %d: '%s'") % tok_num % *tok);
780       ++tok_num;
781       if (*tok == "(") {
782         items.push_back(parse_item(*tok));
783       } else if (*tok == ")") {
784         unsigned int lparen_pos = 1;
785         while (lparen_pos <= items.size() && idx(items, items.size() - lparen_pos).str != "(")
786           {
787             ++lparen_pos;
788           }
789         E(lparen_pos < items.size(), origin::user,
790           F("selector '%s' is invalid, unmatched ')'") % orig);
791         I(idx(items, items.size() - lparen_pos).str == "(");
792         unsigned int name_idx = items.size() - lparen_pos - 1;
793         if (lparen_pos < items.size() && !idx(items, name_idx).str.empty()
794             && special_chars.find(idx(items, name_idx).str) == string::npos)
795           {
796             // looks like a function call
797             shared_ptr<fn_selector> to_add(new fn_selector(idx(items, name_idx).str));
798             L(FL("found function-like selector '%s' at stack position %d of %d")
799               % items[name_idx].str % name_idx % items.size());
800             // note the closing paren is not on the item stack
801             for (unsigned int i = items.size() - lparen_pos + 1;
802                  i < items.size(); i += 2)
803               {
804                 L(FL("        found argument at stack position %d") % i);
805                 shared_ptr<selector> arg = idx(items,i).sel;
806                 E(i == items.size() - 1 || idx(items,i+1).str == ";", origin::user,
807                   F("selector '%s' is invalid, function argument doesn't look like an arg-list"));
808                 to_add->add(arg);
809               }
810             while (name_idx < items.size())
811               items.pop_back();
812             items.push_back(parse_item(to_add));
813           }
814         else
815           {
816             // just parentheses for grouping, closing paren is not on the item stack
817             E(lparen_pos == 2 && idx(items, items.size() - 1).sel, origin::user,
818               F("selector '%s' is invalid, grouping parentheses contain something that "
819                 "doesn't look like an expr") % orig);
820             shared_ptr<selector> to_add(new nested_selector(idx(items, items.size() - 1).sel));
821             items.pop_back();
822             items.pop_back();
823             items.push_back(parse_item(to_add));
824           }
825       } else if (*tok == ";") {
826         items.push_back(parse_item(*tok));
827       } else if (*tok == "/") {
828         E(!items.empty(), origin::user,
829           F("selector '%s' is invalid, because it starts with a '/'") % orig);
830         items.push_back(parse_item(*tok));
831       } else if (*tok == "|") {
832         E(!items.empty(), origin::user,
833           F("selector '%s' is invalid, because it starts with a '|'") % orig);
834         items.push_back(parse_item(*tok));
835       } else {
836         vector<string>::const_iterator next = tok;
837         ++next;
838         bool next_is_oparen = false;
839         if (next != splitted.end())
840           next_is_oparen = (*next == "(");
841         if (next_is_oparen)
842           items.push_back(parse_item(*tok));
843         else
844           items.push_back(parse_item(create_simple_selector(opts, lua,
845                                                             project,
846                                                             *tok)));
847       }
848 
849       // may have an infix operator to reduce
850       if (items.size() >= 3 && items.back().sel)
851         {
852           string op = idx(items, items.size() - 2).str;
853           if (op == "|" || op == "/")
854             {
855               shared_ptr<selector> lhs = idx(items, items.size() - 3).sel;
856               shared_ptr<selector> rhs = idx(items, items.size() - 1).sel;
857               E(lhs, origin::user,
858                 F("selector '%s' is invalid, because there is a '%s' someplace it shouldn't be")
859                 % orig % op);
860               shared_ptr<or_selector> lhs_as_or = boost::dynamic_pointer_cast<or_selector>(lhs);
861               shared_ptr<and_selector> lhs_as_and = boost::dynamic_pointer_cast<and_selector>(lhs);
862               E(op == "/" || !lhs_as_and, origin::user,
863                 F("selector '%s' is invalid, don't mix '/' and '|' operators without parentheses")
864                 % orig);
865               E(op == "|" || !lhs_as_or, origin::user,
866                 F("selector '%s' is invalid, don't mix '/' and '|' operators without parentheses")
867                 % orig);
868               shared_ptr<selector> new_item;
869               if (lhs_as_or)
870                 {
871                   lhs_as_or->add(rhs);
872                   new_item = lhs;
873                 }
874               else if (lhs_as_and)
875                 {
876                   lhs_as_and->add(rhs);
877                   new_item = lhs;
878                 }
879               else
880                 {
881                   if (op == "/")
882                     {
883                       shared_ptr<and_selector> x(new and_selector());
884                       x->add(lhs);
885                       x->add(rhs);
886                       new_item = x;
887                     }
888                   else
889                     {
890                       shared_ptr<or_selector> x(new or_selector());
891                       x->add(lhs);
892                       x->add(rhs);
893                       new_item = x;
894                     }
895                 }
896               I(new_item);
897               items.pop_back();
898               items.pop_back();
899               items.pop_back();
900               items.push_back(parse_item(new_item));
901             }
902         }
903     }
904   E(items.size() == 1 && items[0].sel, origin::user,
905     F("selector '%s' is invalid, it doesn't look like an expr") % orig);
906   return items[0].sel;
907 }
908 
909 
910 void
complete(options const & opts,lua_hooks & lua,project_t & project,string const & str,set<revision_id> & completions)911 complete(options const & opts, lua_hooks & lua,
912          project_t & project,
913          string const & str,
914          set<revision_id> & completions)
915 {
916   shared_ptr<selector> sel = selector::create(opts, lua, project, str);
917 
918   // avoid logging if there's no expansion to be done
919   shared_ptr<ident_selector> isel = boost::dynamic_pointer_cast<ident_selector>(sel);
920   if (isel && isel->is_full_length())
921     {
922       completions.insert(isel->get_assuming_full_length());
923       E(project.db.revision_exists(*completions.begin()), origin::user,
924         F("no revision %s found in database") % *completions.begin());
925       return;
926     }
927 
928   P(F("expanding selection '%s'") % str);
929   completions = sel->complete(project);
930 
931   E(!completions.empty(), origin::user,
932     F("no match for selection '%s'") % str);
933 
934   for (set<revision_id>::const_iterator i = completions.begin();
935        i != completions.end(); ++i)
936     {
937       P(F("expanded to '%s'") % *i);
938 
939       // This may be impossible, but let's make sure.
940       // All the callers used to do it.
941       E(project.db.revision_exists(*i), origin::user,
942         F("no revision %s found in database") % *i);
943     }
944 }
945 
946 void
complete(options const & opts,lua_hooks & lua,project_t & project,string const & str,revision_id & completion)947 complete(options const & opts, lua_hooks & lua,
948          project_t & project,
949          string const & str,
950          revision_id & completion)
951 {
952   set<revision_id> completions;
953 
954   complete(opts, lua, project, str, completions);
955 
956   I(!completions.empty());
957   diagnose_ambiguous_expansion(opts, lua, project, str, completions);
958 
959   completion = *completions.begin();
960 }
961 
962 
963 void
expand_selector(options const & opts,lua_hooks & lua,project_t & project,string const & str,set<revision_id> & completions)964 expand_selector(options const & opts, lua_hooks & lua,
965                 project_t & project,
966                 string const & str,
967                 set<revision_id> & completions)
968 {
969   shared_ptr<selector> sel = selector::create(opts, lua, project, str);
970   completions = sel->complete(project);
971 }
972 
973 // Local Variables:
974 // mode: C++
975 // fill-column: 76
976 // c-file-style: "gnu"
977 // indent-tabs-mode: nil
978 // End:
979 // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
980