ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf/match.pm
Revision: 1.38
Committed: Sat Nov 17 23:40:02 2018 UTC (5 years, 6 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.37: +1 -0 lines
Log Message:
copyright update 2018

File Contents

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