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