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.15 by root, Mon Oct 12 17:37:43 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>
118 162
119=item in map 163=item in map
120 164
121Replaces 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.
122 166
167=item in head
168
169Replaces all objects by their head objects.
170
123=item in <condition> 171=item in <condition>
124 172
125Finds all context objects matching the condition, and then puts their 173Finds all context objects matching the condition, and then puts their
126inventories into the context set. 174inventories into the context set.
127 175
140 188
141Example: 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.
142 190
143 type=SPELL also in inv 191 type=SPELL also in inv
144 192
145=item deep in ... 193=item also deep in ...
146 194
147Repeats 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
148recursively look into objects. 196recursively look into objects.
149 197
150=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.
151 201
152C<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.
153 204
154Example: check if there are any unpaid items in an inventory, 205Example: check if there are any unpaid items in an inventory,
155or in the inventories of the inventory objects, and so on. 206or in the inventories of the inventory objects, and so on.
156 207
157 unpaid also deep in inv 208 unpaid also deep in inv
276=item any 327=item any
277 328
278This 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
279bit easier to read. 330bit easier to read.
280 331
332=item none
333
334This simply evaluates to false, and simply makes matching I<never> a bit
335easier to read.
336
281=item has(condition) 337=item has(condition)
282 338
283True iff the object has a matching inventory object. 339True iff the object has a matching inventory object.
284 340
285=item count(match) 341=item count(match)
286 342
287Number 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
288currently 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
289example. 345example.
290
291=item match(match)
292
293An independent match - semantics like C<count>, except it only matters
294whether the match finds any object (which is faster).
295 346
296=item dump() 347=item dump()
297 348
298Dumps 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.
299 350
314 365
315 match = chain 366 match = chain
316 | chain 'of' root 367 | chain 'of' root
317 root = 'object' | 'self' | 'source' | 'originator' 368 root = 'object' | 'self' | 'source' | 'originator'
318 chain = condition 369 chain = condition
319 | chain also deep 'in' set 370 | chain also deep 'in' modifier
320 also = nothing | 'also' 371 also = nothing | 'also'
321 deep = nothing | 'deep' 372 deep = nothing | 'deep'
322 set = 'inv' | 'env' | 'arch' | 'map' 373 modifier ='inv' | 'env' | 'arch' | 'map' | 'head'
323 374
324 empty = 375 nothing =
325 376
326 # boolean matching condition 377 # boolean matching condition
327 378
328 condition = factor 379 condition = factor
329 | factor 'and'? condition 380 | factor 'and'? condition
330 | factor 'or' condition 381 | factor 'or' condition
331 382
332 factor = 'not' factor 383 factor = 'not' factor
333 | '(' condition ')' 384 | '(' match ')'
334 | expr 385 | expr
335 | expr operator constant 386 | expr operator constant
336 387
337 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>=' 388 operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
338 389
339 expr = flag 390 expr = flag
340 | sattr 391 | sattr
341 | aattr '[' <constant> ']' 392 | aattr '[' <constant> ']'
393 | 'stat.' statattr
342 | special 394 | special
343 | func '(' args ')' 395 | func '(' args ')'
344 | '{' perl code block '}' 396 | '{' perl code block '}'
345 397
346 func = <any function name> 398 func = <any function name>
347 sattr = <any scalar object attribute> 399 sattr = <any scalar object attribute>
348 aattr = <any array object attribute> 400 aattr = <any array object attribute>
349 flag = <any object flag> 401 flag = <any object flag>
402 statattr = <any stat attribute: exp, food, str, dex, hp, maxhp...>
350 special = <any ()-less "function"> 403 special = <any ()-less "function">
351 404
352 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name> 405 constant = <number> | '"' <string> '"' | <uppercase cf::XXX name>
353 args = <depends on function> 406 args = <depends on function>
354 407
365package cf::match; 418package cf::match;
366 419
367use common::sense; 420use common::sense;
368 421
369use List::Util (); 422use List::Util ();
370
371# parser state
372# $_ # string to be parsed
373our $all; # find all, or just the first matching object
374 423
375{ 424{
376 package cf::match::exec; 425 package cf::match::exec;
377 426
378 use List::Util qw(first); 427 use List::Util qw(first);
383 432
384 sub ws { 433 sub ws {
385 /\G\s+/gc; 434 /\G\s+/gc;
386 } 435 }
387 436
437 sub condition ();
438 sub match ($$);
439
388 our %func = ( 440 our %func = (
389 has => sub { 441 has => sub {
390 'first { ' . &condition . ' } $_->inv' 442 'first { ' . condition . ' } $_->inv'
391 }, 443 },
392 count => sub { 444 count => sub {
393 local $all = 1;
394 '(scalar ' . &match ('$_') . ')' 445 '(scalar ' . (match 1, '$_') . ')'
395 },
396 match => sub {
397 local $all = 0;
398 '(scalar ' . &match ('$_') . ')'
399 }, 446 },
400 dump => sub { 447 dump => sub {
401 'do { 448 'do {
402 warn "cf::match::match dump:\n" 449 warn "cf::match::match dump:\n"
403 . "self: " . eval { $self->name } . "\n" 450 . "self: " . eval { $self->name } . "\n"
409 456
410 our %special = ( 457 our %special = (
411 any => sub { 458 any => sub {
412 1 459 1
413 }, 460 },
461 none => sub {
462 0
463 },
414 ); 464 );
415 465
416 sub constant { 466 sub constant {
417 ws; 467 ws;
418 468
426 } 476 }
427 477
428 our $flag = $cf::REFLECT{object}{flags}; 478 our $flag = $cf::REFLECT{object}{flags};
429 our $sattr = $cf::REFLECT{object}{scalars}; 479 our $sattr = $cf::REFLECT{object}{scalars};
430 our $aattr = $cf::REFLECT{object}{arrays}; 480 our $aattr = $cf::REFLECT{object}{arrays};
481 our $lattr = $cf::REFLECT{living}{scalars};
431 482
432 sub expr { 483 sub expr {
433 # ws done by factor 484 # ws done by factor
434 my $res; 485 my $res;
435 486
438 489
439 my $expr = $1; 490 my $expr = $1;
440 491
441 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr"; 492 $res .= $expr =~ /\{([^;]+)\}/ ? $1 : "do $expr";
442 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
443 } elsif (/\G([A-Za-z0-9_]+)/gc) { 504 } elsif (/\G([A-Za-z0-9_]+)/gc) {
444 505
445 if (my $func = $func{$1}) { 506 if (my $func = $func{$1}) {
446 /\G\s*\(/gc 507 /\G\s*\(/gc
447 or die "'(' expected after function name\n"; 508 or die "'(' expected after function name\n";
475 } else { 536 } else {
476 $res .= constant; 537 $res .= constant;
477 } 538 }
478 539
479 } else { 540 } else {
541 Carp::cluck;#d#
480 die "expr expected\n"; 542 die "expr expected\n";
481 } 543 }
482 544
483 $res 545 $res
484 } 546 }
501 $res .= "!"; 563 $res .= "!";
502 } 564 }
503 565
504 if (/\G\(/gc) { 566 if (/\G\(/gc) {
505 # () 567 # ()
506 $res .= &condition; 568
507 ws; 569 $res .= '(' . (match 0, '$_') . ')';
570
508 /\G\)/gc or die "')' expected\n"; 571 /\G\s*\)/gc or die "closing ')' expected\n";
509 572
510 } else { 573 } else {
511 my $expr = expr; 574 my $expr = expr;
512 575
513 $res .= $expr; 576 $res .= $expr;
524 } 587 }
525 588
526 "($res)" 589 "($res)"
527 } 590 }
528 591
529 sub condition { 592 sub condition () {
530 my $res = factor; 593 my $res = factor;
531 594
532 while () { 595 while () {
533 ws; 596 ws;
534 597
535 # 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
536 if (/\G(?=also\b|deep\b|in\b|of\b\)|\z)/gc) { 599 if (/\G(?=also\b|deep\b|in\b|of\b|\)|\z)/gc) {
537 pos = pos; # argh. the misop hits again. again. again. again. you die. 600 pos = pos; # argh. the misop hits again. again. again. again. you die.
538 last; 601 last;
539 602
540 } elsif (/\Gor\b/gc) { 603 } elsif (/\Gor\b/gc) {
541 $res .= " || "; 604 $res .= " || ";
548 } 611 }
549 612
550 $res 613 $res
551 } 614 }
552 615
553 sub match { 616 sub match ($$) {
554 my $default = shift; 617 my ($wantarray, $defctx) = @_;
555 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
556 my $res = ($all ? " grep { " : " first {") . condition . " }"; 629 $res = ($wantarray ? " grep { " : " first { ") . $res . "}";
557 630
558 while () { 631 while () {
559 ws; 632 ws;
560 633
561 my $also = /\Galso\s+/gc + 0; 634 my $also = /\Galso\s+/gc + 0;
562 my $deep = /\Gdeep\s+/gc + 0; 635 my $deep = /\Gdeep\s+/gc + 0;
563 636
564 if (/\Gin\s+/gc) { 637 if (/\Gin\s+/gc) {
565 my $expand; 638 my $expand;
566 639
567 if (/\G(inv|env|map|arch)\b/gc) { 640 if (/\G(inv|env|map|arch|head)\b/gc) {
568 if ($1 eq "inv") { 641 if ($1 eq "inv") {
569 $expand = "map \$_->inv,"; 642 $expand = "map \$_->inv,";
570 } elsif ($1 eq "env") { 643 } elsif ($1 eq "env") {
571 $expand = "map \$_->env // (),"; 644 $expand = "map \$_->env // (),";
645 } elsif ($1 eq "head") {
646 $expand = "map \$_->head,";
647 $deep = 0; # infinite loop otherwise
572 } elsif ($1 eq "arch") { 648 } elsif ($1 eq "arch") {
573 $expand = "map \$_->arch,"; 649 $expand = "map \$_->arch,";
650 $deep = 0; # infinite loop otherwise
574 } elsif ($1 eq "map") { 651 } elsif ($1 eq "map") {
575 $expand = "map \$_->map->at (\$_->x, \$_->y),"; 652 $expand = "map \$_->map->at (\$_->x, \$_->y),";
653 $deep = 0; # infinite loop otherwise
576 } 654 }
577 } else { 655 } else {
578 $expand = "map \$_->inv, grep { " . condition . " }"; 656 $expand = "map \$_->inv, grep { " . condition . " }";
579 } 657 }
580 658
588 $res .= " (\@res, \@_)\n" 666 $res .= " (\@res, \@_)\n"
589 . "}"; 667 . "}";
590 } else { 668 } else {
591 $res .= " $expand"; 669 $res .= " $expand";
592 } 670 }
671 } else {
672
593 } elsif (/\Gof\s+(self|object|source|originator)\b/gc) { 673 if (/\Gof\s+(self|object|source|originator)\b/gc) {
594 $also || $deep 674 $also || $deep
595 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";
596 676
597 if ($1 eq "self") { 677 if ($1 eq "self") {
598 return "$res \$self // ()"; 678 return "$res \$self // ()";
599 } elsif ($1 eq "object") { 679 } elsif ($1 eq "object") {
600 return "$res \$object"; 680 return "$res \$object";
601 } elsif ($1 eq "source") { 681 } elsif ($1 eq "source") {
602 return "$res \$source // ()"; 682 return "$res \$source // ()";
603 } elsif ($1 eq "originator") { 683 } elsif ($1 eq "originator") {
604 return "$res \$originator // \$source // ()"; 684 return "$res \$originator // \$source // ()";
685 }
686 } else {
687 return "$res $defctx";
605 } 688 }
606 } else {
607 return "$res $default";
608 } 689 }
609 } 690 }
610 } 691 }
611} 692}
612 693
613sub parse($;$) { 694sub parse($$) { # wantarray, matchexpr
614 local $_ = shift;
615 local $all = shift;
616
617 my $res; 695 my $res;
618 696
697 local $_ = $_[1];
698
619 eval { 699 eval {
620 $res = cf::match::parser::match "\$object"; 700 $res = cf::match::parser::match $_[0], "\$object";
621 701
622 /\G$/gc 702 /\G$/gc
623 or die "unexpected trailing characters after match\n"; 703 or die "unexpected trailing characters after match\n";
624 }; 704 };
625 705
634 714
635 $res 715 $res
636} 716}
637 717
638if (0) {#d# 718if (0) {#d#
639 die parse 'type=SPELL_EFFECT and match(name="bullet" in arch)', 1; 719 die parse 1, 'stats.pow';
640 exit 0; 720 exit 0;
641} 721}
642 722
643=item cf::match::match $match, $object[, $self[, $source[, $originator]]]
644
645Compiles (and caches) the C<$match> expression and matches it against
646the C<$object>. C<$self> should be the object initiating the match (or
647C<undef>), C<$source> should be the actor/source and C<$originator> the
648object that initiated the action (such as the player). C<$originator>
649defaults to C<$source> when not given.
650
651In list context it finds and returns all matching objects, in scalar
652context only a true or false value.
653
654=cut
655
656our %CACHE; 723our %CACHE;
657 724
658sub compile($$) { 725sub compile($$) {
659 my ($match, $all) = @_; 726 my ($wantarray, $match) = @_;
660 my $expr = parse $match, $all; 727 my $expr = parse $wantarray, $match;
661 warn "MATCH DEBUG $match,$all => $expr\n";#d# 728 warn "MATCH DEBUG $match,$wantarray => $expr\n";#d#
662 $expr = eval " 729 $expr = eval "
663 package cf::match::exec; 730 package cf::match::exec;
664 sub { 731 sub {
665 my (\$object, \$self, \$source, \$originator) = \@_; 732 my (\$object, \$self, \$source, \$originator) = \@_;
666 $expr 733 $expr
669 die if $@; 736 die if $@;
670 737
671 $expr 738 $expr
672} 739}
673 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
674sub match($$;$$$) { 754sub match($$;$$$) {
675 my $match = shift; 755 my $match = shift;
676 my $all = wantarray+0; 756 my $wantarray = wantarray+0;
677 757
678 &{ 758 &{
679 $CACHE{"$all$match"} ||= compile $match, $all 759 $CACHE{"$wantarray$match"} ||= compile $wantarray, $match
680 } 760 }
681} 761}
682 762
763our $CACHE_CLEARER = AE::timer 3600, 3600, sub {
764 %CACHE = ();
765};
766
683#d# $::schmorp=cf::player::find "schmorp"& 767#d# $::schmorp=cf::player::find "schmorp"&
684#d# cf::match::match '', $::schmorp->ob 768#d# cf::match::match '', $::schmorp->ob
685 769
686 770
687=back 771=back

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines