ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf/match.pm
Revision: 1.27
Committed: Sun Apr 11 02:20:53 2010 UTC (14 years, 3 months ago) by root
Branch: MAIN
CVS Tags: rel-3_0
Changes since 1.26: +1 -1 lines
Log Message:
think matches basically work

File Contents

# Content
1 #
2 # This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 #
4 # Copyright (©) 2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 #
6 # Deliantra is free software: you can redistribute it and/or modify it under
7 # the terms of the Affero GNU General Public License as published by the
8 # Free Software Foundation, either version 3 of the License, or (at your
9 # option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the Affero GNU General Public License
17 # and the GNU General Public License along with this program. If not, see
18 # <http://www.gnu.org/licenses/>.
19 #
20 # The authors can be reached via e-mail to <support@deliantra.net>
21 #
22
23 =head1 NAME
24
25 cf::match - object matching language
26
27 =head1 DESCRIPTION
28
29 This module implements a simple object matching language. It can be asked
30 to find any ("check for a match"), or all ("find all objects") matching
31 objects.
32
33 =head1 MATCH EXAMPLES
34
35 Match the object if it has a slaying field of C<key1>:
36
37 slaying = "key1"
38
39 Match the object if it has an object with name C<force> and
40 slaying C<poison> in it's inventory:
41
42 has (name = "force" and slaying = "poison")
43
44 Find all inventory objects with value >= 10, which are not invisible:
45
46 value >= 10 and not invisible in inv
47
48 Find all potions with spell objects inside them in someones inventory:
49
50 type=SPELL in type=POTION in inv
51
52 Find all scrolls inside someones inventory, or inside applied scroll
53 containers:
54
55 type=SCROLL also in applied type=CONTAINER race="scroll" in inv
56
57 Find all unpaid items, anywhere, even deeply nested inside other items, in
58 the originator:
59
60 unpaid also deep in inv of originator
61
62 =head1 MATCH EXPRESSIONS
63
64 =head2 STRUCTURE
65
66 The two main structures are the C<match>, which selects objects matching
67 various criteria, and the C<condition, which determines if an object
68 matches some desired properties:
69
70 condition
71 condition in set-modifier
72 condition of root-object
73
74 A C<condition> receives a set of "context objects" that it is applied
75 to. This is initially just one object - by default, for altars, it is the
76 object dropped on it, for pedestals, the object on top of it and so on.
77
78 This set of context objects can be modified in various ways, for example
79 by replacing it with the inventories of all objects, or all items on the
80 same mapspace, and so on, by using the C<in> operator:
81
82 condition in inv
83 condition in map
84
85 Also, besides the default root object where all this begins, you can start
86 elsewhere, for example in the I<originator> (usually the player):
87
88 condition in inv of originator
89
90 Once the final set of context objects has been established, each object
91 is matched against the C<condition>.
92
93 It is possible to chain modifiers from right-to-left, so this example
94 would start with the originator, take it's inventory, find all inventory
95 items which are potions, looks into their inventory, and then finds all
96 spells.
97
98 type=SPELL in type=POTION in inv of originator
99
100 Sometimes the server is only interested in knowing whether I<anything>
101 matches, and sometimes the server is interested in I<all> objects that
102 match.
103
104 =head2 OPERATORS
105
106 =over 4
107
108 =item and, or, not, ()
109
110 Conditions can be combined with C<and> or C<or> to build larger
111 expressions. C<not> negates the condition, and parentheses can be used to
112 override operator precedence and execute submatches.
113
114 Not that C<not> only negates a condition and not the whole match
115 expressions, thus
116
117 not applied in inv
118
119 is true if there is I<any> non-applied object in the inventory. To negate
120 a whole match, you have to use a sub-match: To check whether there is
121 I<no> applied object in someones inventory, write this:
122
123 not (applied in inv)
124
125 Example: match applied weapons.
126
127 applied type=WEAPON
128
129 Example: match horns or rods.
130
131 type=HORN or type=ROD
132
133 Example: see if the originator is a player.
134
135 type=PLAYER of originator
136
137 =item in ...
138
139 The in operator takes the context set and modifies it in various ways. As
140 a less technical description, think of the C<in> as being a I<look into>
141 or I<look at> operator - instead of looking at whatever was provided to
142 the match, the C<in> operator lets you look at other sets of objects, most
143 often the inventory.
144
145 =over 4
146
147 =item in inv
148
149 Replaces all objects by their inventory.
150
151 Example: find all spell objects inside the object to be matched.
152
153 type=SPELL in inv
154
155 =item in env
156
157 Replaces all objects by their containing object, if they have one.
158
159 =item in arch
160
161 Replaces all objects by their archetypes.
162
163 =item in map
164
165 Replaces all objects by the objects that are on the same mapspace as them.
166
167 =item in head
168
169 Replaces all objects by their head objects.
170
171 =item in <condition>
172
173 Finds all context objects matching the condition, and then puts their
174 inventories into the context set.
175
176 Note that C<in inv> is simply a special case of an C<< in <condition> >> that
177 matches any object.
178
179 Example: find all spells inside potions inside the inventory of the context
180 object(s).
181
182 type=SPELL in type=POTION in inv
183
184 =item also in ...
185
186 Instead of replacing the context set with something new, the new objects
187 are added to the existing set.
188
189 Example: check if the context object I<is> a spell, or I<contains> a spell.
190
191 type=SPELL also in inv
192
193 =item also deep in ...
194
195 Repeats the operation as many times as possible. This can be used to
196 recursively look into objects.
197
198 So for example, C<also deep in inv> means to take the inventory of all
199 objects, taking their inventories, and so on, and adding all these objects
200 to the context set.
201
202 Similarly, C<also deep in env> means to take the environment object, their
203 environemnt object and so on.
204
205 Example: check if there are any unpaid items in an inventory,
206 or in the inventories of the inventory objects, and so on.
207
208 unpaid also deep in inv
209
210 Example: check if a object is inside a player.
211
212 type=PLAYER also deep in env
213
214 =back
215
216 =item of ...
217
218 By default, all matches are applied to the "obviously appropriate" object,
219 such as the item dropped on a button or moving over a detector. This can
220 be changed to a number of other objects - not all of them are available
221 for each match (when not available, the match will simply fail).
222
223 An C<of> term ends a match, nothing is allowed to follow.
224
225 =over 4
226
227 =item of object
228
229 Starts with the default object - this is the object passed to the match to
230 match against by default. Matches have an explicit C<of object> appended,
231 but submatches start at the current object, and in this case C<of object>
232 can be used to start at the original object once more.
233
234 =item of source
235
236 Starts with the I<source> object - this object is sometimes passed to
237 matches and represents the object that is the source of the action, such
238 as a rod or a potion when it is applied. Often, the I<source> is the same
239 as the I<originator>.
240
241 =item of originator
242
243 Starts with the I<originator> - one step farther removed than the
244 I<source>, the I<originator> is sometimes passed to matches and represents
245 the original initiator of an action, most commonly a player or monster.
246
247 This object is often identical to the I<source> (e.g. when a player casts
248 a spell, the player is both source and originator).
249
250 =item of self
251
252 Starts with the object initiating/asking for the match - this is basically
253 always the object that the match expression is attached to.
254
255 =back
256
257 =head2 EXPRESSIONS
258
259 Expressions used in conditions usually consist of simple boolean checks
260 (flag XYZ is set) or simple comparisons.
261
262 =over 4
263
264 =item flags
265
266 Flag names (without the leading C<FLAG_>) can be used as-is, in which case
267 their corresponding flag value is used.
268
269 =item scalar object attributes
270
271 Object attributes that consist of a single value (C<name>, C<title>,
272 C<value> and so on) can be specified by simply using their name, in which
273 acse their corresponding value is used.
274
275 =item array objects attributes
276
277 The C<resist> array can be accessed by specifying C<< resist [ ATNR_type ]
278 >>.
279
280 Example: match an acid resistance higher than 30.
281
282 resist[ATNR_ACID] > 30
283
284 =item functions
285
286 Some additional functions with or without arguments in parentheses are
287 available.
288
289 =item { BLOCK }
290
291 You can specify perl code to execute by putting it inside curly
292 braces. The last expression evaluated inside will become the result.
293
294 The perlcode can access C<$_>, which rferes to the object currently being
295 matches, and the C<$object>, C<$self>, C<$source> and C<$originator>.
296
297 Example: check whether the slaying field consists of digits only.
298
299 { $_->slaying =~ /^\d+$/ }
300
301 =item comparisons, <, <=, ==, =, !=, =>, >
302
303 You can compare expressions against constants via any of these
304 operators. If the constant is a string, then a string compare will be
305 done, otherwise a numerical comparison is used.
306
307 Example: match an object with name "schnops" that has a value >= 10.
308
309 name="schnops" and value >= 10
310
311 =item uppercase constant names
312
313 Any uppercase word that exists as constant inside the C<cf::> namespace
314 (that is, any deliantra constant) can also be used as-is, but needs to be
315 specified in uppercase.
316
317 Example: match a type of POTION (using C<cf::POTION>).
318
319 type=POTION
320
321 =back
322
323 =head2 FUNCTIONS
324
325 =over 4
326
327 =item any
328
329 This simply evaluates to true, and simply makes matching I<any> object a
330 bit easier to read.
331
332 =item none
333
334 This simply evaluates to false, and simply makes matching I<never> a bit
335 easier to read.
336
337 =item has(condition)
338
339 True iff the object has a matching inventory object.
340
341 =item count(match)
342
343 Number of matching objects - the context object for the C<match> is the
344 currently tested object - you can override this with an C<in object> for
345 example.
346
347 =item dump()
348
349 Dumps the object to the server log when executed, and evaluates to true.
350
351 Note that logical operations are short-circuiting, so this only dumps
352 potions:
353
354 type=POTION and dump()
355
356 =back
357
358 =head2 GRAMMAR
359
360 This is the grammar that was used to implement the matching language
361 module. It is meant to be easily readable by humans, not to implement it
362 exactly as-is.
363
364 # object matching and selecting
365
366 match = chain
367 | chain 'of' root
368 root = 'object' | 'self' | 'source' | 'originator'
369 chain = condition
370 | chain also deep 'in' modifier
371 also = nothing | 'also'
372 deep = nothing | 'deep'
373 modifier ='inv' | 'env' | 'arch' | 'map' | 'head'
374
375 nothing =
376
377 # boolean matching condition
378
379 condition = factor
380 | factor 'and'? condition
381 | factor 'or' condition
382
383 factor = 'not' factor
384 | '(' match ')'
385 | expr
386 | expr operator constant
387
388 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
389
390 expr = flag
391 | sattr
392 | aattr '[' <constant> ']'
393 | 'stat.' statattr
394 | special
395 | func '(' args ')'
396 | '{' perl code block '}'
397
398 func = <any function name>
399 sattr = <any scalar object attribute>
400 aattr = <any array object attribute>
401 flag = <any object flag>
402 statattr = <any stat attribute: exp, food, str, dex, hp, maxhp...>
403 special = <any ()-less "function">
404
405 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name>
406 args = <depends on function>
407
408 TODO: contains, matches, query_name, selling_price, buying_price?
409
410 =cut
411
412 =head2 PERL FUNCTIONS
413
414 =over 4
415
416 =cut
417
418 package cf::match;
419
420 use common::sense;
421
422 use List::Util ();
423
424 {
425 package cf::match::exec;
426
427 use List::Util qw(first);
428
429 package cf::match::parser;
430
431 use common::sense;
432
433 sub ws {
434 /\G\s+/gc;
435 }
436
437 sub condition ();
438 sub match ($$);
439
440 our %func = (
441 has => sub {
442 'first { ' . condition . ' } $_->inv'
443 },
444 count => sub {
445 '(scalar ' . (match 1, '$_') . ')'
446 },
447 dump => sub {
448 'do {
449 warn "cf::match::match dump:\n"
450 . "self: " . eval { $self->name } . "\n"
451 . $_->as_string;
452 1
453 }';
454 },
455 );
456
457 our %special = (
458 any => sub {
459 1
460 },
461 none => sub {
462 0
463 },
464 );
465
466 sub constant {
467 ws;
468
469 return $1 if /\G([\-\+0-9\.]+)/gc;
470 return "cf::$1" if /\G([A-Z0-9_]+)/gc;
471
472 #TODO better string parsing, also include ''
473 return $1 if /\G("[^"]+")/gc;
474
475 die "number, string or uppercase constant name expected\n";
476 }
477
478 our $flag = $cf::REFLECT{object}{flags};
479 our $sattr = $cf::REFLECT{object}{scalars};
480 # quick hack to support archname, untested
481 $sattr->{archname} = "W";
482 our $aattr = $cf::REFLECT{object}{arrays};
483 our $lattr = $cf::REFLECT{living}{scalars};
484
485 sub expr {
486 # ws done by factor
487 my $res;
488
489 if (/\G ( \{ (?: (?> [^{}]+ ) | (?-1) )* \} ) /gcx) {
490 # perl
491
492 my $expr = $1;
493
494 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr";
495
496 } elsif (/\Gstats\.([A-Za-z0-9_]+)/gc) {
497
498 if (exists $lattr->{$1}) {
499 $res .= "\$_->stats->$1";
500 } elsif (exists $lattr->{"\u$1"}) {
501 $res .= "\$_->stats->\u$1";
502 } else {
503 die "living statistic name expected (str, pow, hp, sp...)\n";
504 }
505
506 } elsif (/\G([A-Za-z0-9_]+)/gc) {
507
508 if (my $func = $func{$1}) {
509 /\G\s*\(/gc
510 or die "'(' expected after function name\n";
511
512 $res .= $func->();
513
514 /\G\s*\)/gc
515 or die "')' expected after function arguments\n";
516
517 } elsif (my $func = $special{$1}) {
518 $res .= $func->();
519
520 } elsif (exists $flag->{lc $1}) {
521 $res .= "\$_->flag (cf::FLAG_\U$1)";
522
523 } elsif (exists $sattr->{$1}) {
524 $res .= "\$_->$1";
525
526 } elsif (exists $aattr->{$1}) {
527
528 $res .= "\$_->$1";
529
530 /\G\s*\[/gc
531 or die "'[' expected after array name\n";
532
533 $res .= "(" . constant . ")";
534
535 /\G\s*\]/gc
536 or die "']' expected after array index\n";
537
538 } else {
539 $res .= constant;
540 }
541
542 } else {
543 Carp::cluck;#d#
544 die "expr expected\n";
545 }
546
547 $res
548 }
549
550 our %stringop = (
551 "==" => "eq",
552 "!=" => "ne",
553 "<=" => "le",
554 ">=" => "ge",
555 "<" => "lt",
556 ">" => "gt",
557 );
558
559 sub factor {
560 ws;
561
562 my $res;
563
564 if (/\Gnot\b\s*/gc) {
565 $res .= "!";
566 }
567
568 if (/\G\(/gc) {
569 # ()
570
571 $res .= '(' . (match 0, '$_') . ')';
572
573 /\G\s*\)/gc or die "closing ')' expected\n";
574
575 } else {
576 my $expr = expr;
577
578 $res .= $expr;
579
580 if (/\G\s*([=!<>]=?)/gc) {
581 my $op = $1;
582
583 $op = "==" if $op eq "=";
584 my $const = constant;
585 $op = $stringop{$op} if $const =~ /^"/;
586
587 $res .= " $op $const";
588 }
589 }
590
591 "($res)"
592 }
593
594 sub condition () {
595 my $res = factor;
596
597 while () {
598 ws;
599
600 # first check some stop-symbols, so we don't have to backtrack
601 if (/\G(?=also\b|deep\b|in\b|of\b|\)|\z)/gc) {
602 pos = pos; # argh. the misop hits again. again. again. again. you die.
603 last;
604
605 } elsif (/\Gor\b/gc) {
606 $res .= " || ";
607
608 } else {
609 /\Gand\b/gc;
610 $res .= " && ";
611 }
612 $res .= factor;
613 }
614
615 $res
616 }
617
618 sub match ($$) {
619 my ($wantarray, $defctx) = @_;
620
621 my $res = condition;
622
623 # if nothing follows, we have a simple condition, so
624 # optimise a comon case.
625 if ($defctx eq '$_' and /\G\s*(?=\)|$)/gc) {
626 return $wantarray
627 ? "$res ? \$_ : ()"
628 : $res;
629 }
630
631 $res = ($wantarray ? " grep { " : " first { ") . $res . "}";
632
633 while () {
634 ws;
635
636 my $also = /\Galso\s+/gc + 0;
637 my $deep = /\Gdeep\s+/gc + 0;
638
639 if (/\Gin\s+/gc) {
640 my $expand;
641
642 if (/\G(inv|env|map|arch|head)\b/gc) {
643 if ($1 eq "inv") {
644 $expand = "map \$_->inv,";
645 } elsif ($1 eq "env") {
646 $expand = "map \$_->env // (),";
647 } elsif ($1 eq "head") {
648 $expand = "map \$_->head,";
649 $deep = 0; # infinite loop otherwise
650 } elsif ($1 eq "arch") {
651 $expand = "map \$_->arch,";
652 $deep = 0; # infinite loop otherwise
653 } elsif ($1 eq "map") {
654 $expand = "map \$_->map->at (\$_->x, \$_->y),";
655 $deep = 0; # infinite loop otherwise
656 }
657 } else {
658 $expand = "map \$_->inv, grep { " . condition . " }";
659 }
660
661 if ($also || $deep) {
662 $res .= " do {\n"
663 . " my \@res;\n";
664 $res .= " while (\@_) {\n" if $deep;
665 $res .= " push \@res, \@_;\n" if $also;
666 $res .= " \@_ = $expand \@_;\n";
667 $res .= " }\n" if $deep;
668 $res .= " (\@res, \@_)\n"
669 . "}";
670 } else {
671 $res .= " $expand";
672 }
673 } else {
674
675 if (/\Gof\s+(self|object|source|originator)\b/gc) {
676 $also || $deep
677 and die "neither 'also' nor 'deep' can be used with 'of'\n";
678
679 if ($1 eq "self") {
680 return "$res \$self // ()";
681 } elsif ($1 eq "object") {
682 return "$res \$object";
683 } elsif ($1 eq "source") {
684 return "$res \$source // ()";
685 } elsif ($1 eq "originator") {
686 return "$res \$originator // \$source // ()";
687 }
688 } else {
689 return "$res $defctx";
690 }
691 }
692 }
693 }
694 }
695
696 sub parse($$) { # wantarray, matchexpr
697 my $res;
698
699 local $_ = $_[1];
700
701 eval {
702 $res = cf::match::parser::match $_[0], "\$object";
703
704 /\G$/gc
705 or die "unexpected trailing characters after match\n";
706 };
707
708 if ($@) {
709 my $ctx = 20;
710 my $str = substr $_, (List::Util::max 0, (pos) - $ctx), $ctx * 2;
711 substr $str, (List::Util::min $ctx, pos), 0, "<-- HERE -->";
712
713 chomp $@;
714 die "$@ ($str)\n";
715 }
716
717 $res
718 }
719
720 if (0) {#d#
721 die parse 1, 'stats.pow';
722 exit 0;
723 }
724
725 our %CACHE;
726
727 sub compile($$) {
728 my ($wantarray, $match) = @_;
729 my $expr = parse $wantarray, $match;
730 # warn "MATCH DEBUG $match,$wantarray => $expr\n";#d#
731 $expr = eval "
732 package cf::match::exec;
733 sub {
734 my (\$object, \$self, \$source, \$originator) = \@_;
735 $expr
736 }
737 ";
738 die if $@;
739
740 $expr
741 }
742
743 =item cf::match::match $match, $object[, $self[, $source[, $originator]]]
744
745 Compiles (and caches) the C<$match> expression and matches it against
746 the C<$object>. C<$self> should be the object initiating the match (or
747 C<undef>), C<$source> should be the actor/source and C<$originator> the
748 object that initiated the action (such as the player). C<$originator>
749 defaults to C<$source> when not given.
750
751 In list context it finds and returns all matching objects, in scalar
752 context only a true or false value.
753
754 =cut
755
756 sub match($$;$$$) {
757 my $match = shift;
758 my $wantarray = wantarray+0;
759
760 &{
761 $CACHE{"$wantarray$match"} ||= compile $wantarray, $match
762 }
763 }
764
765 our $CACHE_CLEARER = AE::timer 3600, 3600, sub {
766 %CACHE = ();
767 };
768
769 #d# $::schmorp=cf::player::find "schmorp"&
770 #d# cf::match::match '', $::schmorp->ob
771
772
773 =back
774
775 =head1 AUTHOR
776
777 Marc Lehmann <schmorp@schmorp.de>
778 http://home.schmorp.de/
779
780 =cut
781
782 1;
783