ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf/match.pm
(Generate patch)

Comparing deliantra/server/lib/cf/match.pm (file contents):
Revision 1.12 by root, Sun Oct 11 23:51:41 2009 UTC vs.
Revision 1.24 by root, Tue Nov 3 23:44:21 2009 UTC

1#
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
1=head1 NAME 23=head1 NAME
2 24
3cf::match - object matching language 25cf::match - object matching language
4 26
5=head1 DESCRIPTION 27=head1 DESCRIPTION
66 condition in inv of originator 88 condition in inv of originator
67 89
68Once the final set of context objects has been established, each object 90Once the final set of context objects has been established, each object
69is matched against the C<condition>. 91is matched against the C<condition>.
70 92
93It is possible to chain modifiers from right-to-left, so this example
94would start with the originator, take it's inventory, find all inventory
95items which are potions, looks into their inventory, and then finds all
96spells.
97
98 type=SPELL in type=POTION in inv of originator
99
71Sometimes the server is only interested in knowing whether I<anything> 100Sometimes the server is only interested in knowing whether I<anything>
72matches, and sometimes the server is interested in I<all> objects that 101matches, and sometimes the server is interested in I<all> objects that
73match. 102match.
74 103
75=head2 OPERATORS 104=head2 OPERATORS
77=over 4 106=over 4
78 107
79=item and, or, not, () 108=item and, or, not, ()
80 109
81Conditions can be combined with C<and> or C<or> to build larger 110Conditions can be combined with C<and> or C<or> to build larger
82expressions. C<not> negates the expression, and parentheses can be used to 111expressions. C<not> negates the condition, and parentheses can be used to
83group conditions. 112override operator precedence and execute submatches.
113
114Not that C<not> only negates a condition and not the whole match
115expressions, thus
116
117 not applied in inv
118
119is true if there is I<any> non-applied object in the inventory. To negate
120a whole match, you have to use a sub-match: To check whether there is
121I<no> applied object in someones inventory, write this:
122
123 not (applied in inv)
84 124
85Example: match applied weapons. 125Example: match applied weapons.
86 126
87 applied type=WEAPON 127 applied type=WEAPON
88 128
89Example: match horns or rods. 129Example: match horns or rods.
90 130
91 type=HORN or type=ROD 131 type=HORN or type=ROD
132
133Example: see if the originator is a player.
134
135 type=PLAYER of originator
92 136
93=item in ... 137=item in ...
94 138
95The in operator takes the context set and modifies it in various ways. As 139The in operator takes the context set and modifies it in various ways. As
96a less technical description, think of the C<in> as being a I<look into> 140a less technical description, think of the C<in> as being a I<look into>
110 154
111=item in env 155=item in env
112 156
113Replaces all objects by their containing object, if they have one. 157Replaces all objects by their containing object, if they have one.
114 158
159=item in arch
160
161Replaces all objects by their archetypes.
162
115=item in map 163=item in map
116 164
117Replaces all objects by the objects that are on the same mapspace as them. 165Replaces all objects by the objects that are on the same mapspace as them.
118 166
167=item in head
168
169Replaces all objects by their head objects.
170
119=item in <cond> 171=item in <condition>
120 172
121Finds all context objects matching the condition, and then puts their 173Finds all context objects matching the condition, and then puts their
122inventories into the context set. 174inventories into the context set.
123 175
124Note that C<in inv> is simply a special case of an C<< in <cond> >> that 176Note that C<in inv> is simply a special case of an C<< in <condition> >> that
125matches any object. 177matches any object.
126 178
127Example: find all spells inside potions inside the inventory of the context 179Example: find all spells inside potions inside the inventory of the context
128object(s). 180object(s).
129 181
136 188
137Example: check if the context object I<is> a spell, or I<contains> a spell. 189Example: check if the context object I<is> a spell, or I<contains> a spell.
138 190
139 type=SPELL also in inv 191 type=SPELL also in inv
140 192
141=item deep in ... 193=item also deep in ...
142 194
143Repeats the operation as many times as possible. This can be used to 195Repeats the operation as many times as possible. This can be used to
144recursively look into objects. 196recursively look into objects.
145 197
146=item also deep in ... 198So for example, C<also deep in inv> means to take the inventory of all
199objects, taking their inventories, and so on, and adding all these objects
200to the context set.
147 201
148C<also> and C<deep> can be combined. 202Similarly, C<also deep in env> means to take the environment object, their
203environemnt object and so on.
149 204
150Example: check if there are any unpaid items in an inventory, 205Example: check if there are any unpaid items in an inventory,
151or in the inventories of the inventory objects, and so on. 206or in the inventories of the inventory objects, and so on.
152 207
153 unpaid also deep in inv 208 unpaid also deep in inv
272=item any 327=item any
273 328
274This simply evaluates to true, and simply makes matching I<any> object a 329This simply evaluates to true, and simply makes matching I<any> object a
275bit easier to read. 330bit easier to read.
276 331
332=item none
333
334This simply evaluates to false, and simply makes matching I<never> a bit
335easier to read.
336
277=item has(condition) 337=item has(condition)
278 338
279True iff the object has a matching inventory object. 339True iff the object has a matching inventory object.
280 340
281=item count(match) 341=item count(match)
282 342
283Number of matching objects - the context object for the C<match> is the 343Number of matching objects - the context object for the C<match> is the
284currently tested object - you can override this with an C<in object> for 344currently tested object - you can override this with an C<in object> for
285example. 345example.
286 346
287=item match(match)
288
289An independent match - semantics like C<count>, except it only matters
290whether the match finds any object (which is faster).
291
292=item dump 347=item dump()
293 348
294Dumps the object to the server log when executed, and evaluates to true. 349Dumps the object to the server log when executed, and evaluates to true.
295 350
296Note that logical operations are short-circuiting, so this only dumps 351Note that logical operations are short-circuiting, so this only dumps
297potions: 352potions:
298 353
299 type=POTION and dump 354 type=POTION and dump()
300 355
301=back 356=back
302 357
303=head2 GRAMMAR 358=head2 GRAMMAR
304 359
310 365
311 match = chain 366 match = chain
312 | chain 'of' root 367 | chain 'of' root
313 root = 'object' | 'self' | 'source' | 'originator' 368 root = 'object' | 'self' | 'source' | 'originator'
314 chain = condition 369 chain = condition
315 | chain also deep 'in' set 370 | chain also deep 'in' modifier
316 also = nothing | 'also' 371 also = nothing | 'also'
317 deep = nothing | 'deep' 372 deep = nothing | 'deep'
318 set = 'inv' | 'env' | 'map' 373 modifier ='inv' | 'env' | 'arch' | 'map' | 'head'
319 374
320 empty = 375 nothing =
321 376
322 # boolean matching condition 377 # boolean matching condition
323 378
324 condition = factor 379 condition = factor
325 | factor 'and'? cond 380 | factor 'and'? condition
326 | factor 'or' cond 381 | factor 'or' condition
327 382
328 factor = 'not' factor 383 factor = 'not' factor
329 | '(' cond ')' 384 | '(' match ')'
330 | expr 385 | expr
331 | expr operator constant 386 | expr operator constant
332 387
333 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>=' 388 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
334 389
335 expr = flag 390 expr = flag
336 | sattr 391 | sattr
337 | aattr '[' <constant> ']' 392 | aattr '[' <constant> ']'
393 | 'stat.' statattr
338 | special 394 | special
339 | func '(' args ')' 395 | func '(' args ')'
340 | '{' perl code block '}' 396 | '{' perl code block '}'
341 397
342 func = <any function name> 398 func = <any function name>
343 sattr = <any scalar object attribute> 399 sattr = <any scalar object attribute>
344 aattr = <any array object attribute> 400 aattr = <any array object attribute>
345 flag = <any object flag> 401 flag = <any object flag>
402 statattr = <any stat attribute: exp, food, str, dex, hp, maxhp...>
346 special = <any ()-less "function"> 403 special = <any ()-less "function">
347 404
348 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name> 405 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name>
349 args = <depends on function> 406 args = <depends on function>
350 407
361package cf::match; 418package cf::match;
362 419
363use common::sense; 420use common::sense;
364 421
365use List::Util (); 422use List::Util ();
366
367# parser state
368# $_ # string to be parsed
369our $all; # find all, or just the first matching object
370 423
371{ 424{
372 package cf::match::exec; 425 package cf::match::exec;
373 426
374 use List::Util qw(first); 427 use List::Util qw(first);
379 432
380 sub ws { 433 sub ws {
381 /\G\s+/gc; 434 /\G\s+/gc;
382 } 435 }
383 436
437 sub condition ();
438 sub match ($$);
439
384 our %func = ( 440 our %func = (
385 has => sub { 441 has => sub {
386 'first { ' . &condition . ' } $_->inv' 442 'first { ' . condition . ' } $_->inv'
387 }, 443 },
388 count => sub { 444 count => sub {
389 local $all = 1;
390 '(scalar ' . &match ('$_') . ')' 445 '(scalar ' . (match 1, '$_') . ')'
391 },
392 match => sub {
393 local $all = 0;
394 '(scalar ' . &match ('$_') . ')'
395 },
396 );
397
398 our %special = (
399 any => sub {
400 1
401 }, 446 },
402 dump => sub { 447 dump => sub {
403 'do { 448 'do {
404 warn "cf::match::match dump:\n" 449 warn "cf::match::match dump:\n"
405 . "self: " . eval { $self->name } . "\n" 450 . "self: " . eval { $self->name } . "\n"
407 1 452 1
408 }'; 453 }';
409 }, 454 },
410 ); 455 );
411 456
457 our %special = (
458 any => sub {
459 1
460 },
461 none => sub {
462 0
463 },
464 );
465
412 sub constant { 466 sub constant {
413 ws; 467 ws;
414 468
415 return $1 if /\G([\-\+0-9\.]+)/gc; 469 return $1 if /\G([\-\+0-9\.]+)/gc;
416 return "cf::$1" if /\G([A-Z0-9_]+)/gc; 470 return "cf::$1" if /\G([A-Z0-9_]+)/gc;
422 } 476 }
423 477
424 our $flag = $cf::REFLECT{object}{flags}; 478 our $flag = $cf::REFLECT{object}{flags};
425 our $sattr = $cf::REFLECT{object}{scalars}; 479 our $sattr = $cf::REFLECT{object}{scalars};
426 our $aattr = $cf::REFLECT{object}{arrays}; 480 our $aattr = $cf::REFLECT{object}{arrays};
481 our $lattr = $cf::REFLECT{living}{scalars};
427 482
428 sub expr { 483 sub expr {
429 # ws done by factor 484 # ws done by factor
430 my $res; 485 my $res;
431 486
434 489
435 my $expr = $1; 490 my $expr = $1;
436 491
437 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr"; 492 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr";
438 493
494 } 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
439 } elsif (/\G([A-Za-z0-9_]+)/gc) { 504 } elsif (/\G([A-Za-z0-9_]+)/gc) {
440 505
441 if (my $func = $func{$1}) { 506 if (my $func = $func{$1}) {
442 /\G\s*\(/gc 507 /\G\s*\(/gc
443 or die "'(' expected after function name\n"; 508 or die "'(' expected after function name\n";
471 } else { 536 } else {
472 $res .= constant; 537 $res .= constant;
473 } 538 }
474 539
475 } else { 540 } else {
541 Carp::cluck;#d#
476 die "expr expected\n"; 542 die "expr expected\n";
477 } 543 }
478 544
479 $res 545 $res
480 } 546 }
497 $res .= "!"; 563 $res .= "!";
498 } 564 }
499 565
500 if (/\G\(/gc) { 566 if (/\G\(/gc) {
501 # () 567 # ()
502 $res .= &condition; 568
503 ws; 569 $res .= '(' . (match 0, '$_') . ')';
570
504 /\G\)/gc or die "')' expected\n"; 571 /\G\s*\)/gc or die "closing ')' expected\n";
505 572
506 } else { 573 } else {
507 my $expr = expr; 574 my $expr = expr;
508 575
509 $res .= $expr; 576 $res .= $expr;
520 } 587 }
521 588
522 "($res)" 589 "($res)"
523 } 590 }
524 591
525 sub condition { 592 sub condition () {
526 my $res = factor; 593 my $res = factor;
527 594
528 while () { 595 while () {
529 ws; 596 ws;
530 597
531 # first check some stop-symbols, so we don't have to backtrack 598 # first check some stop-symbols, so we don't have to backtrack
532 if (/\G(?=also\b|deep\b|in\b|of\b\)|$)/gc) { 599 if (/\G(?=also\b|deep\b|in\b|of\b|\)|\z)/gc) {
600 pos = pos; # argh. the misop hits again. again. again. again. you die.
533 last; 601 last;
534 602
535 } elsif (/\Gor\b/gc) { 603 } elsif (/\Gor\b/gc) {
536 $res .= " || "; 604 $res .= " || ";
537 605
543 } 611 }
544 612
545 $res 613 $res
546 } 614 }
547 615
548 sub match { 616 sub match ($$) {
549 my $default = shift; 617 my ($wantarray, $defctx) = @_;
550 618
619 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
551 my $res = ($all ? " grep { " : " first {") . condition . " }"; 629 $res = ($wantarray ? " grep { " : " first { ") . $res . "}";
552 630
553 while () { 631 while () {
554 ws; 632 ws;
555 633
556 my $also = /\Galso\s+/gc + 0; 634 my $also = /\Galso\s+/gc + 0;
557 my $deep = /\Gdeep\s+/gc + 0; 635 my $deep = /\Gdeep\s+/gc + 0;
558 636
559 if (/\Gin\s+/gc) { 637 if (/\Gin\s+/gc) {
560 my $expand; 638 my $expand;
561 639
562 if (/\G(inv|env|map)\b/gc) { 640 if (/\G(inv|env|map|arch|head)\b/gc) {
563 if ($1 eq "inv") { 641 if ($1 eq "inv") {
564 $expand = "map \$_->inv,"; 642 $expand = "map \$_->inv,";
565 } elsif ($1 eq "env") { 643 } elsif ($1 eq "env") {
566 $expand = "map \$_->env // (),"; 644 $expand = "map \$_->env // (),";
645 } elsif ($1 eq "head") {
646 $expand = "map \$_->head,";
647 $deep = 0; # infinite loop otherwise
648 } elsif ($1 eq "arch") {
649 $expand = "map \$_->arch,";
650 $deep = 0; # infinite loop otherwise
567 } elsif ($1 eq "map") { 651 } elsif ($1 eq "map") {
568 $expand = "map \$_->map->at (\$_->x, \$_->y),"; 652 $expand = "map \$_->map->at (\$_->x, \$_->y),";
653 $deep = 0; # infinite loop otherwise
569 } 654 }
570 } else { 655 } else {
571 $expand = "map \$_->inv, grep { " . condition . " }"; 656 $expand = "map \$_->inv, grep { " . condition . " }";
572 } 657 }
573 658
581 $res .= " (\@res, \@_)\n" 666 $res .= " (\@res, \@_)\n"
582 . "}"; 667 . "}";
583 } else { 668 } else {
584 $res .= " $expand"; 669 $res .= " $expand";
585 } 670 }
671 } else {
672
586 } elsif (/\Gof\s+(self|object|source|originator)\b/gc) { 673 if (/\Gof\s+(self|object|source|originator)\b/gc) {
587 $also || $deep 674 $also || $deep
588 and die "neither 'also' nor 'deep' can be used with 'of'\n"; 675 and die "neither 'also' nor 'deep' can be used with 'of'\n";
589 676
590 if ($1 eq "self") { 677 if ($1 eq "self") {
591 return "$res \$self // ()"; 678 return "$res \$self // ()";
592 } elsif ($1 eq "object") { 679 } elsif ($1 eq "object") {
593 return "$res \$object"; 680 return "$res \$object";
594 } elsif ($1 eq "source") { 681 } elsif ($1 eq "source") {
595 return "$res \$source // ()"; 682 return "$res \$source // ()";
596 } elsif ($1 eq "originator") { 683 } elsif ($1 eq "originator") {
597 return "$res \$originator // \$source // ()"; 684 return "$res \$originator // \$source // ()";
685 }
686 } else {
687 return "$res $defctx";
598 } 688 }
599 } else {
600 return "$res $default";
601 } 689 }
602 } 690 }
603 } 691 }
604} 692}
605 693
606sub parse($;$) { 694sub parse($$) { # wantarray, matchexpr
607 local $_ = shift;
608 local $all = shift;
609
610 my $res; 695 my $res;
611 696
697 local $_ = $_[1];
698
612 eval { 699 eval {
613 $res = cf::match::parser::match "\$object"; 700 $res = cf::match::parser::match $_[0], "\$object";
614 701
615 /\G\s*$/gc 702 /\G$/gc
616 or die "unexpected trailing characters after match\n"; 703 or die "unexpected trailing characters after match\n";
617 }; 704 };
618 705
619 if ($@) { 706 if ($@) {
620 my $ctx = 20; 707 my $ctx = 20;
626 } 713 }
627 714
628 $res 715 $res
629} 716}
630 717
631if (1) {#d# 718if (0) {#d#
632 die parse 'applied', 1; 719 die parse 1, 'stats.pow';
633 exit 0; 720 exit 0;
634} 721}
635 722
636=item cf::match::match $match, $object[, $self[, $source[, $originator]]]
637
638Compiles (and caches) the C<$match> expression and matches it against
639the C<$object>. C<$self> should be the object initiating the match (or
640C<undef>), C<$source> should be the actor/source and C<$originator> the
641object that initiated the action (such as the player). C<$originator>
642defaults to C<$source> when not given.
643
644In list context it finds and returns all matching objects, in scalar
645context only a true or false value.
646
647=cut
648
649our %CACHE; 723our %CACHE;
650 724
651sub compile($$) { 725sub compile($$) {
652 my ($match, $all) = @_; 726 my ($wantarray, $match) = @_;
653 my $expr = parse $match, $all; 727 my $expr = parse $wantarray, $match;
654 warn "MATCH DEBUG $match,$all => $expr\n";#d# 728 warn "MATCH DEBUG $match,$wantarray => $expr\n";#d#
655 $expr = eval " 729 $expr = eval "
656 package cf::match::exec; 730 package cf::match::exec;
657 sub { 731 sub {
658 my (\$object, \$self, \$source, \$originator) = \@_; 732 my (\$object, \$self, \$source, \$originator) = \@_;
659 $expr 733 $expr
662 die if $@; 736 die if $@;
663 737
664 $expr 738 $expr
665} 739}
666 740
741=item cf::match::match $match, $object[, $self[, $source[, $originator]]]
742
743Compiles (and caches) the C<$match> expression and matches it against
744the C<$object>. C<$self> should be the object initiating the match (or
745C<undef>), C<$source> should be the actor/source and C<$originator> the
746object that initiated the action (such as the player). C<$originator>
747defaults to C<$source> when not given.
748
749In list context it finds and returns all matching objects, in scalar
750context only a true or false value.
751
752=cut
753
667sub match($$;$$$) { 754sub match($$;$$$) {
668 my $match = shift; 755 my $match = shift;
669 my $all = wantarray+0; 756 my $wantarray = wantarray+0;
670 757
671 &{ 758 &{
672 $CACHE{"$all$match"} ||= compile $match, $all 759 $CACHE{"$wantarray$match"} ||= compile $wantarray, $match
673 } 760 }
674} 761}
675 762
763our $CACHE_CLEARER = AE::timer 3600, 3600, sub {
764 %CACHE = ();
765};
766
676#d# $::schmorp=cf::player::find "schmorp"& 767#d# $::schmorp=cf::player::find "schmorp"&
677#d# cf::match::match '', $::schmorp->ob 768#d# cf::match::match '', $::schmorp->ob
678 769
679 770
680=back 771=back

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines