--- deliantra/server/lib/cf/match.pm 2009/10/11 00:24:35 1.9 +++ deliantra/server/lib/cf/match.pm 2009/10/11 23:51:47 1.13 @@ -27,9 +27,15 @@ type=SPELL in type=POTION in inv -Find all potions inside someones inventory, or inside applied containers: +Find all scrolls inside someones inventory, or inside applied scroll +containers: - type=POTION also in type=CONTAINER and applied in inv + type=SCROLL also in applied type=CONTAINER race="scroll" in inv + +Find all unpaid items, anywhere, even deeply nested inside other items, in +the originator: + + unpaid also deep in inv of originator =head1 MATCH EXPRESSIONS @@ -40,23 +46,31 @@ matches some desired properties: condition - condition in set + condition in set-modifier + condition of root-object -A C receives a set of "context objects" that it is applied to. This -is initially just one object - for altars, it is the object dropped on it, -for pedestals, the object on top of it and so on. +A C receives a set of "context objects" that it is applied +to. This is initially just one object - by default, for altars, it is the +object dropped on it, for pedestals, the object on top of it and so on. This set of context objects can be modified in various ways, for example -by replacing it with the inventories of all objects, or all objects on the +by replacing it with the inventories of all objects, or all items on the same mapspace, and so on, by using the C operator: condition in inv - condition in inv in originator + condition in map + +Also, besides the default root object where all this begins, you can start +elsewhere, for example in the I (usually the player): -Once the set of context objects has been established, each object is -matched against the C expression. Sometimes the server is only -interested in knowing whether I matches, and sometimes the -server is interested in I objects that match. + condition in inv of originator + +Once the final set of context objects has been established, each object +is matched against the C. + +Sometimes the server is only interested in knowing whether I +matches, and sometimes the server is interested in I objects that +match. =head2 OPERATORS @@ -70,7 +84,7 @@ Example: match applied weapons. - type=WEAPON and applied + applied type=WEAPON Example: match horns or rods. @@ -86,37 +100,6 @@ =over 4 -=item in object - -Replaces all objects by the default object - this is the object passed to -the match to match against by default. All matches have an explicit C appended. - -This must be the last C expression in a match. - -=item in source - -Replaces all objects by the source object - this object is sometimes -passed to matches and represents the object is the source of the action, -such as a rod or a potion when it is applied. Often, the I is the -same as the I. - -This must be the last C expression in a match. - -=item in originator - -Replaces all objects by the originator object - one step farther removed -than the I, the I is sometimes passed to matches and -represents the original initiator of an action, most commonly a player or -monster. - -=item in self - -Replaces all objects by the object initiating/asking for the match - this -is basically always the object thatt he match expression is attached to. - -This must be the last C expression in a match. - =item in inv Replaces all objects by their inventory. @@ -155,26 +138,65 @@ type=SPELL also in inv -=item repeatedly in ... +=item deep in ... Repeats the operation as many times as possible. This can be used to recursively look into objects. -=item also repeatedly in ... +=item also deep in ... -C and C can be combined. +C and C can be combined. Example: check if there are any unpaid items in an inventory, or in the inventories of the inventory objects, and so on. - unpaid also repeatedly in inv + unpaid also deep in inv Example: check if a object is inside a player. - type=PLAYER also repeatedly in env + type=PLAYER also deep in env =back +=item of ... + +By default, all matches are applied to the "obviously appropriate" object, +such as the item dropped on a button or moving over a detector. This can +be changed to a number of other objects - not all of them are available +for each match (when not available, the match will simply fail). + +An C term ends a match, nothing is allowed to follow. + +=over 4 + +=item of object + +Starts with the default object - this is the object passed to the match to +match against by default. Matches have an explicit C appended, +but submatches start at the current object, and in this case C +can be used to start at the original object once more. + +=item of source + +Starts with the I object - this object is sometimes passed to +matches and represents the object that is the source of the action, such +as a rod or a potion when it is applied. Often, the I is the same +as the I. + +=item of originator + +Starts with the I - one step farther removed than the +I, the I is sometimes passed to matches and represents +the original initiator of an action, most commonly a player or monster. + +This object is often identical to the I (e.g. when a player casts +a spell, the player is both source and originator). + +=item of self + +Starts with the object initiating/asking for the match - this is basically +always the object that the match expression is attached to. + =back =head2 EXPRESSIONS @@ -214,6 +236,13 @@ You can specify perl code to execute by putting it inside curly braces. The last expression evaluated inside will become the result. +The perlcode can access C<$_>, which rferes to the object currently being +matches, and the C<$object>, C<$self>, C<$source> and C<$originator>. + +Example: check whether the slaying field consists of digits only. + + { $_->slaying =~ /^\d+$/ } + =item comparisons, <, <=, ==, =, !=, =>, > You can compare expressions against constants via any of these @@ -245,19 +274,29 @@ This simply evaluates to true, and simply makes matching I object a bit easier to read. -=item has(cond) +=item has(condition) True iff the object has a matching inventory object. =item count(match) -Number of matching objects - the context object for the C are the -original context objects for the overall C. # TODO bullshit +Number of matching objects - the context object for the C is the +currently tested object - you can override this with an C for +example. =item match(match) -An independent match - unlike C, it only matters whether the match -finds any object (which is faster). +An independent match - semantics like C, except it only matters +whether the match finds any object (which is faster). + +=item dump + +Dumps the object to the server log when executed, and evaluates to true. + +Note that logical operations are short-circuiting, so this only dumps +potions: + + type=POTION and dump =back @@ -269,13 +308,14 @@ # object matching and selecting - match = set - | match also rep 'in' set + match = chain + | chain 'of' root + root = 'object' | 'self' | 'source' | 'originator' + chain = condition + | chain also deep 'in' set also = nothing | 'also' - rep = nothing | 'rep' | 'repeatedly' - + deep = nothing | 'deep' set = 'inv' | 'env' | 'map' - | 'object' | 'source' | 'originator' | 'self' empty = @@ -308,10 +348,15 @@ constant = | '"' '"' | args = - TODO: repeatedly, env, contains, possbly matches + TODO: contains, matches, query_name, selling_price, buying_price? =cut +=head2 PERL FUNCTIONS + +=over 4 + +=cut package cf::match; @@ -328,13 +373,6 @@ use List::Util qw(first); - sub env_chain { - my @res; - push @res, $_ - while $_ = $_->env; - @res - } - package cf::match::parser; use common::sense; @@ -349,11 +387,11 @@ }, count => sub { local $all = 1; - '(scalar ' . &match . ')' + '(scalar ' . &match ('$_') . ')' }, match => sub { local $all = 0; - '(scalar ' . &match . ')' + '(scalar ' . &match ('$_') . ')' }, ); @@ -361,6 +399,14 @@ any => sub { 1 }, + dump => sub { + 'do { + warn "cf::match::match dump:\n" + . "self: " . eval { $self->name } . "\n" + . $_->as_string; + 1 + }'; + }, ); sub constant { @@ -481,11 +527,14 @@ while () { ws; - if (/\G(?=also\b|in\b|\)|$)/gc) { - # early stop => faster and requires no backtracking + + # first check some stop-symbols, so we don't have to backtrack + if (/\G(?=also\b|deep\b|in\b|of\b\)|$)/gc) { last; + } elsif (/\Gor\b/gc) { $res .= " || "; + } else { /\Gand\b/gc; $res .= " && "; @@ -497,48 +546,75 @@ } sub match { - my $res; + my $default = shift; + + my $res = ($all ? " grep { " : " first {") . condition . " }"; - my $also; # undef means first iteration while () { - if (/\G\s*(inv|env|map|object|subject|originator)\b/gc) { - if ($1 eq "inv") { - $res .= " map+(${also}\$_->inv),"; - } elsif ($1 eq "env") { - $res .= " map+(${also}env_chain), "; # TODO - } elsif ($1 eq "map") { - $res .= " map+(${also}\$_->map->at (\$_->x, \$_->y)),"; - } elsif ($1 eq "self") { - return "$res \$self"; + ws; + + my $also = /\Galso\s+/gc + 0; + my $deep = /\Gdeep\s+/gc + 0; + + if (/\Gin\s+/gc) { + my $expand; + + if (/\G(inv|env|map)\b/gc) { + if ($1 eq "inv") { + $expand = "map \$_->inv,"; + } elsif ($1 eq "env") { + $expand = "map \$_->env // (),"; + } elsif ($1 eq "map") { + $expand = "map \$_->map->at (\$_->x, \$_->y),"; + } + } else { + $expand = "map \$_->inv, grep { " . condition . " }"; + } + + if ($also || $deep) { + $res .= " do {\n" + . " my \@res;\n"; + $res .= " while (\@_) {\n" if $deep; + $res .= " push \@res, \@_;\n" if $also; + $res .= " \@_ = $expand \@_;\n"; + $res .= " }\n" if $deep; + $res .= " (\@res, \@_)\n" + . "}"; + } else { + $res .= " $expand"; + } + } elsif (/\Gof\s+(self|object|source|originator)\b/gc) { + $also || $deep + and die "neither 'also' nor 'deep' can be used with 'of'\n"; + + if ($1 eq "self") { + return "$res \$self // ()"; } elsif ($1 eq "object") { - last; # default + return "$res \$object"; } elsif ($1 eq "source") { - return "$res \$source"; + return "$res \$source // ()"; } elsif ($1 eq "originator") { - return "$res \$originator"; + return "$res \$originator // \$source // ()"; } - last unless /\G\s*in\b/gc; } else { - $res .= " map+($also\$_->inv)," if defined $also; - $res .= $all ? " grep { " : " first {"; - $res .= condition; - $res .= "}"; - - $also = /\G\s*also\b/gc ? '$_, ' : ''; - last unless /\G\s*in\b/gc; + return "$res $default"; } } - - "$res \$object" } - } sub parse($;$) { local $_ = shift; local $all = shift; - my $res = cf::match::parser::match; + my $res; + + eval { + $res = cf::match::parser::match "\$object"; + + /\G\s*$/gc + or die "unexpected trailing characters after match\n"; + }; if ($@) { my $ctx = 20; @@ -552,27 +628,63 @@ $res } -our %CACHE; +if (0) {#d# + die parse 'applied', 1; + exit 0; +} -sub match($$$;$$) { - my ($self, $match, $object, $source, $originator) = @_; - my $all = wantarray+0; +=item cf::match::match $match, $object[, $self[, $source[, $originator]]] - $originator ||= $source; +Compiles (and caches) the C<$match> expression and matches it against +the C<$object>. C<$self> should be the object initiating the match (or +C), C<$source> should be the actor/source and C<$originator> the +object that initiated the action (such as the player). C<$originator> +defaults to C<$source> when not given. - &{ - $CACHE{"$all$match"} ||= do { - my $expr = parse $match, $all; - warn "$match,$all => $expr\n";#d# - $expr = eval "package cf::match::exec; sub { $expr }"; - die if $@; +In list context it finds and returns all matching objects, in scalar +context only a true or false value. + +=cut + +our %CACHE; + +sub compile($$) { + my ($match, $all) = @_; + my $expr = parse $match, $all; + warn "MATCH DEBUG $match,$all => $expr\n";#d# + $expr = eval " + package cf::match::exec; + sub { + my (\$object, \$self, \$source, \$originator) = \@_; $expr } + "; + die if $@; + + $expr +} + +sub match($$;$$$) { + my $match = shift; + my $all = wantarray+0; + + &{ + $CACHE{"$all$match"} ||= compile $match, $all } } -#match undef, "any", undef; -#exit 0; +#d# $::schmorp=cf::player::find "schmorp"& +#d# cf::match::match '', $::schmorp->ob + + +=back + +=head1 AUTHOR + + Marc Lehmann + http://home.schmorp.de/ + +=cut 1;