ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf/match.pm
Revision: 1.36
Committed: Tue Nov 6 21:52:55 2012 UTC (11 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-3_1
Changes since 1.35: +5 -5 lines
Log Message:
ws

File Contents

# Content
1 #
2 # This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 #
4 # Copyright (©) 2009,2010,2011,2012 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 self
235
236 Starts with the object initiating/asking for the match - this is basically
237 always the object that the match expression is attached to.
238
239 =item of source
240
241 Starts with the I<source> object - this object is sometimes passed to
242 matches and represents the object that is the source of the action, such
243 as a rod or a potion when it is applied. Often, the I<source> is the same
244 as the I<originator>.
245
246 =item of originator
247
248 Starts with the I<originator> - one step farther removed than the
249 I<source>, the I<originator> is sometimes passed to matches and represents
250 the original initiator of an action, most commonly a player or monster.
251
252 This object is often identical to the I<source> (e.g. when a player casts
253 a spell, the player is both source and originator).
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 case 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. They are documented in their own section, below.
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 archname
338
339 The same as C<< { $_->arch->archname } >> - the archetype name is commonly
340 used to match items, so this shortcut is provided.
341
342 =item resist_xxx
343
344 Resistancy values such as C<resist_physical>, C<resist_magic>,
345 C<resists_fire> etc. are directly available (but can also be accessed via
346 array syntax, i.e. C<resists[ATNR_FIRE]>).
347
348 =item body_xxx_info and body_xxx_used
349
350 Every body location (e.g. C<body_neck_info>, C<body_arm_used> etc.) can
351 be accessed via these functions (these are aliases to more cumbersome C<< {
352 $_->slot_info (body_xxx) } >> and C<slot_used> method calls).
353
354 Example: (e.g. on a door) match only players that have no arms.
355
356 match type=PLAYER and body_arm_info=0
357
358 =item has(condition)
359
360 True iff the object has a matching inventory object.
361
362 =item count(match)
363
364 Number of matching objects - the context object for the C<match> is the
365 currently tested object - you can override this with an C<in object> for
366 example.
367
368 =item dump()
369
370 Dumps the object to the server log when executed, and evaluates to true.
371
372 Note that logical operations are short-circuiting, so this only dumps
373 potions:
374
375 type=POTION and dump()
376
377 =back
378
379 =head2 GRAMMAR
380
381 This is the grammar that was used to implement the matching language
382 module. It is meant to be easily readable by humans, not to implement it
383 exactly as-is.
384
385 # object matching and selecting
386
387 match = chain
388 | chain 'of' root
389 root = 'object' | 'self' | 'source' | 'originator'
390 chain = condition
391 | chain also deep 'in' modifier
392 also = nothing | 'also'
393 deep = nothing | 'deep'
394 modifier ='inv' | 'env' | 'arch' | 'map' | 'head'
395
396 nothing =
397
398 # boolean matching condition
399
400 condition = factor
401 | factor 'and'? condition
402 | factor 'or' condition
403
404 factor = 'not' factor
405 | '(' match ')'
406 | expr
407 | expr operator constant
408
409 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
410
411 expr = flag
412 | sattr
413 | aattr '[' <constant> ']'
414 | 'stat.' statattr
415 | special
416 | func '(' args ')'
417 | '{' perl code block '}'
418
419 func = <any function name>
420 sattr = <any scalar object attribute>
421 aattr = <any array object attribute>
422 flag = <any object flag>
423 statattr = <any stat attribute: exp, food, str, dex, hp, maxhp...>
424 special = <any ()-less "function">
425
426 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name>
427 args = <depends on function>
428
429 TODO: contains, matches, query_name, selling_price, buying_price?
430
431 =cut
432
433 =head2 PERL FUNCTIONS
434
435 =over 4
436
437 =cut
438
439 package cf::match;
440
441 use common::sense;
442
443 use List::Util ();
444
445 {
446 package cf::match::exec;
447
448 use List::Util qw(first);
449
450 package cf::match::parser;
451
452 use common::sense;
453
454 sub ws {
455 /\G\s+/gc;
456 }
457
458 sub condition ();
459 sub match ($$);
460
461 our %func = (
462 has => sub {
463 'first { ' . condition . ' } $_->inv'
464 },
465 count => sub {
466 '(scalar ' . (match 1, '$_') . ')'
467 },
468 dump => sub {
469 'do {
470 warn "cf::match::match dump:\n"
471 . "self: " . eval { $self->name } . "\n"
472 . $_->as_string;
473 1
474 }';
475 },
476 );
477
478 our %special = (
479 any => sub {
480 1
481 },
482 none => sub {
483 0
484 },
485 archname => sub {
486 '$_->arch->archname'
487 },
488 );
489
490 # resist_xxx
491 for my $atnr (0 .. cf::NROFATTACKS - 1) {
492 $special{"resist_" . cf::attacktype_name ($atnr)} = sub { "\$_->resist ($atnr)" };
493 }
494
495 # body_xxx_info and _used
496 for my $slot (0 .. cf::NUM_BODY_LOCATIONS - 1) {
497 my $name = cf::object::slot_name $slot;
498
499 $special{"body_$name\_info"} = sub { "\$_->slot_info ($slot)" };
500 $special{"body_$name\_used"} = sub { "\$_->slot_used ($slot)" };
501 }
502
503 sub constant {
504 ws;
505
506 return $1 if /\G([\-\+0-9\.]+)/gc;
507 return "cf::$1" if /\G([A-Z0-9_]+)/gc;
508
509 #TODO better string parsing, also include ''
510 return $1 if /\G("[^"]+")/gc;
511
512 die "number, string or uppercase constant name expected\n";
513 }
514
515 our $flag = $cf::REFLECT{object}{flags};
516 our $sattr = $cf::REFLECT{object}{scalars};
517 our $aattr = $cf::REFLECT{object}{arrays};
518 our $lattr = $cf::REFLECT{living}{scalars};
519
520 sub expr {
521 # ws done by factor
522 my $res;
523
524 if (/\G ( \{ (?: (?> [^{}]+ ) | (?-1) )* \} ) /gcx) {
525 # perl
526
527 my $expr = $1;
528
529 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr";
530
531 } elsif (/\Gstats\.([A-Za-z0-9_]+)/gc) {
532
533 if (exists $lattr->{$1}) {
534 $res .= "\$_->stats->$1";
535 } elsif (exists $lattr->{"\u$1"}) {
536 $res .= "\$_->stats->\u$1";
537 } else {
538 die "living statistic name expected (str, pow, hp, sp...)\n";
539 }
540
541 } elsif (/\G([A-Za-z0-9_]+)/gc) {
542
543 if (my $func = $func{$1}) {
544 /\G\s*\(/gc
545 or die "'(' expected after function name\n";
546
547 $res .= $func->();
548
549 /\G\s*\)/gc
550 or die "')' expected after function arguments\n";
551
552 } elsif (my $func = $special{$1}) {
553 $res .= $func->();
554
555 } elsif (exists $flag->{lc $1}) {
556 $res .= "\$_->flag (cf::FLAG_\U$1)";
557
558 } elsif (exists $sattr->{$1}) {
559 $res .= "\$_->$1";
560
561 } elsif (exists $aattr->{$1}) {
562
563 $res .= "\$_->$1";
564
565 /\G\s*\[/gc
566 or die "'[' expected after array name\n";
567
568 $res .= "(" . constant . ")";
569
570 /\G\s*\]/gc
571 or die "']' expected after array index\n";
572
573 } else {
574 $res .= constant;
575 }
576
577 } else {
578 Carp::cluck;#d#
579 die "expr expected\n";
580 }
581
582 $res
583 }
584
585 our %stringop = (
586 "==" => "eq",
587 "!=" => "ne",
588 "<=" => "le",
589 ">=" => "ge",
590 "<" => "lt",
591 ">" => "gt",
592 );
593
594 sub factor {
595 ws;
596
597 my $res;
598
599 if (/\Gnot\b\s*/gc) {
600 $res .= "!";
601 }
602
603 if (/\G\(/gc) {
604 # ()
605
606 $res .= '(' . (match 0, '$_') . ')';
607
608 /\G\s*\)/gc or die "closing ')' expected\n";
609
610 } else {
611 my $expr = expr;
612
613 $res .= $expr;
614
615 if (/\G\s*([=!<>]=?)/gc) {
616 my $op = $1;
617
618 $op = "==" if $op eq "=";
619 my $const = constant;
620 $op = $stringop{$op} if $const =~ /^"/;
621
622 $res .= " $op $const";
623 }
624 }
625
626 "($res)"
627 }
628
629 sub condition () {
630 my $res = factor;
631
632 while () {
633 ws;
634
635 # first check some stop-symbols, so we don't have to backtrack
636 if (/\G(?=also\b|deep\b|in\b|of\b|\)|\z)/gc) {
637 pos = pos; # argh. the misop hits again. again. again. again. you die.
638 last;
639
640 } elsif (/\Gor\b/gc) {
641 $res .= " || ";
642
643 } else {
644 /\Gand\b/gc;
645 $res .= " && ";
646 }
647 $res .= factor;
648 }
649
650 $res
651 }
652
653 sub match ($$) {
654 my ($wantarray, $defctx) = @_;
655
656 my $res = condition;
657
658 # if nothing follows, we have a simple condition, so
659 # optimise a comon case.
660 if ($defctx eq '$_' and /\G\s*(?=\)|$)/gc) {
661 return $wantarray
662 ? "$res ? \$_ : ()"
663 : $res;
664 }
665
666 $res = ($wantarray ? " grep { " : " first { ") . $res . "}";
667
668 while () {
669 ws;
670
671 my $also = /\Galso\s+/gc + 0;
672 my $deep = /\Gdeep\s+/gc + 0;
673
674 if (/\Gin\s+/gc) {
675 my $expand;
676
677 if (/\G(inv|env|map|arch|head)\b/gc) {
678 if ($1 eq "inv") {
679 $expand = "map \$_->inv,";
680 } elsif ($1 eq "env") {
681 $expand = "map \$_->env // (),";
682 } elsif ($1 eq "head") {
683 $expand = "map \$_->head,";
684 $deep = 0; # infinite loop otherwise
685 } elsif ($1 eq "arch") {
686 $expand = "map \$_->arch,";
687 $deep = 0; # infinite loop otherwise
688 } elsif ($1 eq "map") {
689 $expand = "map \$_->map->at (\$_->x, \$_->y),";
690 $deep = 0; # infinite loop otherwise
691 }
692 } else {
693 $expand = "map \$_->inv, grep { " . condition . " }";
694 }
695
696 if ($also || $deep) {
697 $res .= " do {\n"
698 . " my \@res;\n";
699 $res .= " while (\@_) {\n" if $deep;
700 $res .= " push \@res, \@_;\n" if $also;
701 $res .= " \@_ = $expand \@_;\n";
702 $res .= " }\n" if $deep;
703 $res .= " (\@res, \@_)\n"
704 . "}";
705 } else {
706 $res .= " $expand";
707 }
708 } else {
709
710 if (/\Gof\s+(self|object|source|originator)\b/gc) {
711 $also || $deep
712 and die "neither 'also' nor 'deep' can be used with 'of'\n";
713
714 if ($1 eq "self") {
715 return "$res \$self // ()";
716 } elsif ($1 eq "object") {
717 return "$res \$object";
718 } elsif ($1 eq "source") {
719 return "$res \$source // ()";
720 } elsif ($1 eq "originator") {
721 return "$res \$originator // \$source // ()";
722 }
723 } else {
724 return "$res $defctx";
725 }
726 }
727 }
728 }
729 }
730
731 sub parse($$) { # wantarray, matchexpr
732 my $res;
733
734 local $_ = $_[1];
735
736 eval {
737 $res = cf::match::parser::match $_[0], "\$object";
738
739 /\G$/gc
740 or die "unexpected trailing characters after match\n";
741 };
742
743 if ($@) {
744 my $ctx = 20;
745 my $str = substr $_, (List::Util::max 0, (pos) - $ctx), $ctx * 2;
746 substr $str, (List::Util::min $ctx, pos), 0, "<-- HERE -->";
747
748 chomp $@;
749 die "$@ ($str)\n";
750 }
751
752 $res
753 }
754
755 if (0) {#d#
756 die parse 1, 'type=PLAYER and body_arm_info=0';
757 exit 0;
758 }
759
760 our %CACHE;
761
762 sub compile($$) {
763 my ($wantarray, $match) = @_;
764 my $expr = parse $wantarray, $match;
765 # warn "MATCH DEBUG $match,$wantarray => $expr\n";#d#
766 $expr = eval "
767 package cf::match::exec;
768 sub {
769 my (\$object, \$self, \$source, \$originator) = \@_;
770 $expr
771 }
772 ";
773 die if $@;
774
775 $expr
776 }
777
778 =item cf::match::match $match, $object[, $self[, $source[, $originator]]]
779
780 Compiles (and caches) the C<$match> expression and matches it against
781 the C<$object>. C<$self> should be the object initiating the match (or
782 C<undef>), C<$source> should be the actor/source and C<$originator> the
783 object that initiated the action (such as the player). C<$originator>
784 defaults to C<$source> when not given.
785
786 In list context it finds and returns all matching objects, in scalar
787 context only a true or false value.
788
789 =cut
790
791 sub match($$;$$$) {
792 my $match = shift;
793 my $wantarray = wantarray+0;
794
795 &{
796 $CACHE{"$wantarray$match"} ||= compile $wantarray, $match
797 }
798 }
799
800 our $CACHE_CLEARER = AE::timer 3600, 3600, sub {
801 %CACHE = ();
802 };
803
804 #d# $::schmorp=cf::player::find "schmorp"&
805 #d# cf::match::match '', $::schmorp->ob
806
807
808 =back
809
810 =head1 AUTHOR
811
812 Marc Lehmann <schmorp@schmorp.de>
813 http://home.schmorp.de/
814
815 =cut
816
817 1;
818