ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf/match.pm
Revision: 1.24
Committed: Tue Nov 3 23:44:21 2009 UTC (14 years, 8 months ago) by root
Branch: MAIN
CVS Tags: rel-2_90, rel-2_92, rel-2_93
Changes since 1.23: +22 -0 lines
Log Message:
tighten copyright statements for files containing no gpl code whatsoever anymore

File Contents

# User Rev Content
1 root 1.24 #
2     # This file is part of Deliantra, the Roguelike Realtime MMORPG.
3     #
4     # Copyright (©) 2009 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 root 1.1 =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 root 1.9 to find any ("check for a match"), or all ("find all objects") matching
31     objects.
32 root 1.1
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 root 1.4 Find all inventory objects with value >= 10, which are not invisible:
45 root 1.1
46 root 1.4 value >= 10 and not invisible in inv
47 root 1.1
48     Find all potions with spell objects inside them in someones inventory:
49    
50     type=SPELL in type=POTION in inv
51    
52 root 1.12 Find all scrolls inside someones inventory, or inside applied scroll
53     containers:
54 root 1.1
55 root 1.12 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 root 1.1
62 root 1.9 =head1 MATCH EXPRESSIONS
63 root 1.1
64     =head2 STRUCTURE
65    
66 root 1.9 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 root 1.12 condition in set-modifier
72     condition of root-object
73 root 1.9
74 root 1.12 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 root 1.1
78     This set of context objects can be modified in various ways, for example
79 root 1.12 by replacing it with the inventories of all objects, or all items on the
80 root 1.9 same mapspace, and so on, by using the C<in> operator:
81    
82     condition in inv
83 root 1.12 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 root 1.1
90 root 1.12 Once the final set of context objects has been established, each object
91     is matched against the C<condition>.
92    
93 root 1.19 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 root 1.12 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 root 1.1
104     =head2 OPERATORS
105    
106     =over 4
107    
108     =item and, or, not, ()
109    
110 root 1.9 Conditions can be combined with C<and> or C<or> to build larger
111 root 1.20 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 root 1.23 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 root 1.20
123     not (applied in inv)
124 root 1.1
125     Example: match applied weapons.
126    
127 root 1.12 applied type=WEAPON
128 root 1.1
129     Example: match horns or rods.
130    
131     type=HORN or type=ROD
132    
133 root 1.23 Example: see if the originator is a player.
134    
135     type=PLAYER of originator
136    
137 root 1.1 =item in ...
138    
139 root 1.8 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 root 1.1
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 root 1.14 =item in arch
160    
161     Replaces all objects by their archetypes.
162    
163 root 1.1 =item in map
164    
165     Replaces all objects by the objects that are on the same mapspace as them.
166    
167 root 1.21 =item in head
168    
169     Replaces all objects by their head objects.
170    
171 root 1.15 =item in <condition>
172 root 1.1
173 root 1.9 Finds all context objects matching the condition, and then puts their
174     inventories into the context set.
175 root 1.1
176 root 1.15 Note that C<in inv> is simply a special case of an C<< in <condition> >> that
177 root 1.1 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 root 1.16 =item also deep in ...
194 root 1.1
195     Repeats the operation as many times as possible. This can be used to
196     recursively look into objects.
197    
198 root 1.16 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 root 1.1
202 root 1.16 Similarly, C<also deep in env> means to take the environment object, their
203     environemnt object and so on.
204 root 1.1
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 root 1.12 unpaid also deep in inv
209 root 1.1
210     Example: check if a object is inside a player.
211    
212 root 1.12 type=PLAYER also deep in env
213 root 1.1
214     =back
215    
216 root 1.12 =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 root 1.1 =back
256    
257     =head2 EXPRESSIONS
258    
259 root 1.9 Expressions used in conditions usually consist of simple boolean checks
260     (flag XYZ is set) or simple comparisons.
261 root 1.1
262     =over 4
263    
264 root 1.2 =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 root 1.4 Example: match an acid resistance higher than 30.
281 root 1.2
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 root 1.10 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 root 1.2 =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 root 1.4 Example: match an object with name "schnops" that has a value >= 10.
308 root 1.2
309 root 1.4 name="schnops" and value >= 10
310 root 1.2
311     =item uppercase constant names
312    
313 root 1.4 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 root 1.2 specified in uppercase.
316    
317     Example: match a type of POTION (using C<cf::POTION>).
318    
319     type=POTION
320    
321 root 1.1 =back
322    
323     =head2 FUNCTIONS
324    
325     =over 4
326    
327 root 1.2 =item any
328    
329     This simply evaluates to true, and simply makes matching I<any> object a
330     bit easier to read.
331    
332 root 1.21 =item none
333    
334     This simply evaluates to false, and simply makes matching I<never> a bit
335     easier to read.
336    
337 root 1.11 =item has(condition)
338 root 1.1
339     True iff the object has a matching inventory object.
340    
341 root 1.9 =item count(match)
342    
343 root 1.11 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 root 1.9
347 root 1.14 =item dump()
348 root 1.10
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 root 1.14 type=POTION and dump()
355 root 1.10
356 root 1.1 =back
357    
358 root 1.9 =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 root 1.12 match = chain
367     | chain 'of' root
368     root = 'object' | 'self' | 'source' | 'originator'
369     chain = condition
370 root 1.21 | chain also deep 'in' modifier
371 root 1.9 also = nothing | 'also'
372 root 1.12 deep = nothing | 'deep'
373 root 1.21 modifier ='inv' | 'env' | 'arch' | 'map' | 'head'
374 root 1.9
375 root 1.21 nothing =
376 root 1.9
377     # boolean matching condition
378    
379     condition = factor
380 root 1.15 | factor 'and'? condition
381     | factor 'or' condition
382 root 1.9
383     factor = 'not' factor
384 root 1.20 | '(' match ')'
385 root 1.9 | expr
386     | expr operator constant
387    
388     operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
389    
390     expr = flag
391     | sattr
392     | aattr '[' <constant> ']'
393 root 1.22 | 'stat.' statattr
394 root 1.9 | 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 root 1.22 statattr = <any stat attribute: exp, food, str, dex, hp, maxhp...>
403 root 1.9 special = <any ()-less "function">
404    
405     constant = <number> | '"' <string> '"' | <uppercase cf::XXX name>
406     args = <depends on function>
407    
408 root 1.12 TODO: contains, matches, query_name, selling_price, buying_price?
409 root 1.9
410 root 1.1 =cut
411    
412 root 1.10 =head2 PERL FUNCTIONS
413    
414     =over 4
415    
416     =cut
417 root 1.1
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 root 1.20 sub condition ();
438     sub match ($$);
439    
440 root 1.1 our %func = (
441     has => sub {
442 root 1.20 'first { ' . condition . ' } $_->inv'
443 root 1.1 },
444     count => sub {
445 root 1.20 '(scalar ' . (match 1, '$_') . ')'
446 root 1.1 },
447 root 1.10 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 root 1.1 );
456    
457 root 1.14 our %special = (
458     any => sub {
459     1
460     },
461 root 1.21 none => sub {
462     0
463     },
464 root 1.14 );
465    
466 root 1.1 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     our $aattr = $cf::REFLECT{object}{arrays};
481 root 1.22 our $lattr = $cf::REFLECT{living}{scalars};
482 root 1.1
483     sub expr {
484     # ws done by factor
485     my $res;
486    
487     if (/\G ( \{ (?: (?> [^{}]+ ) | (?-1) )* \} ) /gcx) {
488     # perl
489    
490     my $expr = $1;
491    
492     $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr";
493    
494 root 1.22 } elsif (/\Gstats\.([A-Za-z0-9_]+)/gc) {
495    
496     if (exists $lattr->{$1}) {
497     $res .= "\$_->stats->$1";
498     } elsif (exists $lattr->{"\u$1"}) {
499     $res .= "\$_->stats->\u$1";
500     } else {
501     die "living statistic name expected (str, pow, hp, sp...)\n";
502     }
503    
504 root 1.1 } elsif (/\G([A-Za-z0-9_]+)/gc) {
505    
506     if (my $func = $func{$1}) {
507     /\G\s*\(/gc
508     or die "'(' expected after function name\n";
509    
510     $res .= $func->();
511    
512     /\G\s*\)/gc
513     or die "')' expected after function arguments\n";
514    
515     } elsif (my $func = $special{$1}) {
516     $res .= $func->();
517    
518     } elsif (exists $flag->{lc $1}) {
519     $res .= "\$_->flag (cf::FLAG_\U$1)";
520    
521     } elsif (exists $sattr->{$1}) {
522     $res .= "\$_->$1";
523    
524     } elsif (exists $aattr->{$1}) {
525    
526     $res .= "\$_->$1";
527    
528     /\G\s*\[/gc
529     or die "'[' expected after array name\n";
530    
531     $res .= "(" . constant . ")";
532    
533     /\G\s*\]/gc
534     or die "']' expected after array index\n";
535    
536     } else {
537     $res .= constant;
538     }
539    
540     } else {
541 root 1.20 Carp::cluck;#d#
542 root 1.1 die "expr expected\n";
543     }
544    
545     $res
546     }
547    
548     our %stringop = (
549     "==" => "eq",
550     "!=" => "ne",
551     "<=" => "le",
552     ">=" => "ge",
553     "<" => "lt",
554     ">" => "gt",
555     );
556    
557     sub factor {
558     ws;
559    
560     my $res;
561    
562     if (/\Gnot\b\s*/gc) {
563     $res .= "!";
564     }
565    
566     if (/\G\(/gc) {
567     # ()
568 root 1.20
569     $res .= '(' . (match 0, '$_') . ')';
570    
571     /\G\s*\)/gc or die "closing ')' expected\n";
572 root 1.1
573     } else {
574     my $expr = expr;
575    
576     $res .= $expr;
577    
578     if (/\G\s*([=!<>]=?)/gc) {
579     my $op = $1;
580    
581     $op = "==" if $op eq "=";
582     my $const = constant;
583     $op = $stringop{$op} if $const =~ /^"/;
584    
585     $res .= " $op $const";
586     }
587     }
588    
589     "($res)"
590     }
591    
592 root 1.20 sub condition () {
593 root 1.1 my $res = factor;
594    
595     while () {
596     ws;
597 root 1.12
598     # first check some stop-symbols, so we don't have to backtrack
599 root 1.20 if (/\G(?=also\b|deep\b|in\b|of\b|\)|\z)/gc) {
600 root 1.14 pos = pos; # argh. the misop hits again. again. again. again. you die.
601 root 1.1 last;
602 root 1.12
603 root 1.1 } elsif (/\Gor\b/gc) {
604     $res .= " || ";
605 root 1.12
606 root 1.1 } else {
607     /\Gand\b/gc;
608     $res .= " && ";
609     }
610     $res .= factor;
611     }
612    
613     $res
614     }
615    
616 root 1.20 sub match ($$) {
617     my ($wantarray, $defctx) = @_;
618 root 1.11
619 root 1.20 my $res = condition;
620    
621     # if nothing follows, we have a simple condition, so
622     # optimise a comon case.
623     if ($defctx eq '$_' and /\G\s*(?=\)|$)/gc) {
624     return $wantarray
625     ? "$res ? \$_ : ()"
626     : $res;
627     }
628    
629     $res = ($wantarray ? " grep { " : " first { ") . $res . "}";
630 root 1.1
631     while () {
632 root 1.12 ws;
633    
634     my $also = /\Galso\s+/gc + 0;
635     my $deep = /\Gdeep\s+/gc + 0;
636    
637     if (/\Gin\s+/gc) {
638     my $expand;
639    
640 root 1.21 if (/\G(inv|env|map|arch|head)\b/gc) {
641 root 1.12 if ($1 eq "inv") {
642     $expand = "map \$_->inv,";
643     } elsif ($1 eq "env") {
644     $expand = "map \$_->env // (),";
645 root 1.21 } elsif ($1 eq "head") {
646     $expand = "map \$_->head,";
647     $deep = 0; # infinite loop otherwise
648 root 1.14 } elsif ($1 eq "arch") {
649     $expand = "map \$_->arch,";
650 root 1.17 $deep = 0; # infinite loop otherwise
651 root 1.12 } elsif ($1 eq "map") {
652     $expand = "map \$_->map->at (\$_->x, \$_->y),";
653 root 1.17 $deep = 0; # infinite loop otherwise
654 root 1.12 }
655     } else {
656     $expand = "map \$_->inv, grep { " . condition . " }";
657     }
658    
659     if ($also || $deep) {
660     $res .= " do {\n"
661     . " my \@res;\n";
662     $res .= " while (\@_) {\n" if $deep;
663     $res .= " push \@res, \@_;\n" if $also;
664     $res .= " \@_ = $expand \@_;\n";
665     $res .= " }\n" if $deep;
666     $res .= " (\@res, \@_)\n"
667     . "}";
668     } else {
669     $res .= " $expand";
670     }
671 root 1.20 } else {
672    
673     if (/\Gof\s+(self|object|source|originator)\b/gc) {
674     $also || $deep
675     and die "neither 'also' nor 'deep' can be used with 'of'\n";
676    
677     if ($1 eq "self") {
678     return "$res \$self // ()";
679     } elsif ($1 eq "object") {
680     return "$res \$object";
681     } elsif ($1 eq "source") {
682     return "$res \$source // ()";
683     } elsif ($1 eq "originator") {
684     return "$res \$originator // \$source // ()";
685     }
686     } else {
687     return "$res $defctx";
688 root 1.1 }
689     }
690     }
691     }
692     }
693    
694 root 1.20 sub parse($$) { # wantarray, matchexpr
695     my $res;
696 root 1.1
697 root 1.20 local $_ = $_[1];
698 root 1.12
699     eval {
700 root 1.20 $res = cf::match::parser::match $_[0], "\$object";
701 root 1.12
702 root 1.14 /\G$/gc
703 root 1.12 or die "unexpected trailing characters after match\n";
704     };
705 root 1.1
706     if ($@) {
707     my $ctx = 20;
708     my $str = substr $_, (List::Util::max 0, (pos) - $ctx), $ctx * 2;
709     substr $str, (List::Util::min $ctx, pos), 0, "<-- HERE -->";
710    
711     chomp $@;
712     die "$@ ($str)\n";
713     }
714    
715     $res
716     }
717    
718 root 1.13 if (0) {#d#
719 root 1.22 die parse 1, 'stats.pow';
720 root 1.11 exit 0;
721     }
722    
723 root 1.9 our %CACHE;
724 root 1.1
725 root 1.10 sub compile($$) {
726 root 1.20 my ($wantarray, $match) = @_;
727     my $expr = parse $wantarray, $match;
728     warn "MATCH DEBUG $match,$wantarray => $expr\n";#d#
729 root 1.10 $expr = eval "
730     package cf::match::exec;
731     sub {
732     my (\$object, \$self, \$source, \$originator) = \@_;
733     $expr
734     }
735     ";
736     die if $@;
737    
738     $expr
739     }
740 root 1.9
741 root 1.20 =item cf::match::match $match, $object[, $self[, $source[, $originator]]]
742    
743     Compiles (and caches) the C<$match> expression and matches it against
744     the C<$object>. C<$self> should be the object initiating the match (or
745     C<undef>), C<$source> should be the actor/source and C<$originator> the
746     object that initiated the action (such as the player). C<$originator>
747     defaults to C<$source> when not given.
748    
749     In list context it finds and returns all matching objects, in scalar
750     context only a true or false value.
751    
752     =cut
753    
754 root 1.10 sub match($$;$$$) {
755 root 1.20 my $match = shift;
756     my $wantarray = wantarray+0;
757 root 1.9
758     &{
759 root 1.20 $CACHE{"$wantarray$match"} ||= compile $wantarray, $match
760 root 1.9 }
761 root 1.2 }
762 root 1.1
763 root 1.18 our $CACHE_CLEARER = AE::timer 3600, 3600, sub {
764     %CACHE = ();
765     };
766    
767 root 1.10 #d# $::schmorp=cf::player::find "schmorp"&
768     #d# cf::match::match '', $::schmorp->ob
769    
770    
771     =back
772    
773     =head1 AUTHOR
774    
775     Marc Lehmann <schmorp@schmorp.de>
776     http://home.schmorp.de/
777    
778     =cut
779 root 1.9
780 root 1.1 1;
781