log_prefix
=>
'$'
,
hscratchpad
=>
'%'
,
ascratchpad
=>
'@'
,
sscratchpad
=>
'$'
,
cb
=>
'@'
,
cb_unregistered
=>
'@'
,
topic_fired
=>
'%'
,
topic_fired_data
=>
'%'
,
topic_fired_persistence
=>
'%'
,
topic_level
=>
'@'
,
ncb
=>
'$'
,
prioritized_cb
=>
'@'
,
prioritized_cb_tofire
=>
'@'
,
prioritized_cb_fired
=>
'@'
,
arguments
=>
'@'
,
firing
=>
'$'
;
our
$VERSION
=
'0.14'
;
sub
_sort_by_option_priority_desc {
return
$b
->option->priority <=>
$a
->option->priority;
}
sub
_sort_by_numeric_desc {
return
$b
<=>
$a
;
}
sub
register {
my
(
$self
,
$cb
) =
@_
;
if
(
ref
(
$cb
) ne
'MarpaX::Languages::C::AST::Callback::Method'
) {
croak
'argument bust be a reference to a MarpaX::Languages::C::AST::Callback::Method object'
;
}
if
(!
defined
(
$self
->log_prefix)) {
$self
->log_prefix(
''
);
}
if
(
defined
(
$cb
->method) &&
ref
(
$cb
->method) ne
'ARRAY'
) {
croak
'method must be an ARRAY ref'
;
}
if
(
defined
(
$cb
->method)) {
if
(! @{
$cb
->method}) {
croak
'method is a reference to an empty array'
;
}
if
(
ref
((
$cb
->method)->[0]) ne
'CODE'
&& (!
ref
(
$cb
->method) &&
$cb
->method eq
'auto'
)) {
croak
'method must be an ARRAY ref starting with a CODE reference, or the string \'auto\''
;
}
}
if
(!
defined
(
$cb
->method_mode)) {
$cb
->method_mode(
'push'
);
}
if
(
$cb
->method_mode ne
'push'
&&
$cb
->method_mode ne
'replace'
) {
croak
'method_mode must be \'push\' or \'replace\''
;
}
if
(!
defined
(
$cb
->option)) {
$cb
->option(MarpaX::Languages::C::AST::Callback::Option->new());
}
my
$option
=
$cb
->option;
foreach
(@{
$option
->condition}) {
if
(!
defined
(
$_
) || (! (
ref
(
$_
) eq
'ARRAY'
)) || (! (
ref
(
$_
->[0]) eq
'CODE'
|| (!
ref
(
$_
->[0]) &&
$_
->[0] eq
'auto'
)))) {
croak
'A condition is not an ARRAY reference, that must start with a CODE reference or the "auto" keyword"'
;
}
}
if
(!
defined
(
$option
->conditionMode)) {
$option
->conditionMode(
'and'
);
}
if
(!
grep
{
$option
->conditionMode eq
$_
}
qw/and or/
) {
croak
'condition mode must be "and" or "or"'
;
}
if
(!
defined
(
$option
->subscriptionMode)) {
$option
->subscriptionMode(
'required'
);
}
if
(!
grep
{
$option
->subscriptionMode eq
$_
}
qw/required optional/
) {
croak
'condition mode must be "and" or "or"'
;
}
if
(!
defined
(
$option
->topic_persistence)) {
$option
->topic_persistence(
'none'
);
}
if
(!
grep
{
$option
->topic_persistence eq
$_
}
qw/none any level/
) {
croak
'topic persistence mode must be "none", "any" or "level"'
;
}
if
(!
defined
(
$option
->priority)) {
$option
->priority(0);
}
my
$priority
=
$option
->priority;
if
(! (
"$priority"
=~ /^[+-]?\d+$/)) {
croak
'priority must be a number'
;
}
$self
->ncb(0)
if
(!
defined
(
$self
->ncb));
$self
->cb(
$self
->ncb,
$cb
);
$self
->ncb(
$self
->ncb + 1);
$self
->prioritized_cb([
sort
_sort_by_option_priority_desc @{
$self
->cb}]);
$self
->hscratchpad(
'_cache'
, 0);
return
$self
->ncb - 1;
}
sub
_unregister {
my
$self
=
shift
;
foreach
(
sort
_sort_by_numeric_desc
@_
) {
my
$cb
=
$self
->cb(
$_
);
croak
"Unknown callback indice $_"
if
(!
defined
(
$cb
));
splice
(@{
$self
->cb},
$_
, 1);
$self
->ncb(
$self
->ncb - 1);
$self
->prioritized_cb([
sort
_sort_by_option_priority_desc @{
$self
->cb}]);
}
}
sub
unregister {
my
$self
=
shift
;
my
$firing
=
$self
->firing() || 0;
if
(!
$firing
) {
return
$self
->_unregister(
@_
);
}
else
{
push
(@{
$self
->cb_unregistered},
@_
);
}
}
sub
exec
{
my
$self
=
shift
;
$self
->arguments([
@_
]);
$self
->_inventory_fire();
$self
->_fire();
$self
->_unregister(@{
$self
->cb_unregistered});
$self
->cb_unregistered([]);
}
sub
_inventory_condition_tofire {
my
$self
=
shift
;
my
$nbConditionOK
= 0;
my
$nbNewTopics
= 0;
my
$ncb
=
$self
->ncb;
my
$prioritized_cbp
=
$self
->prioritized_cb;
my
$prioritized_cb_tofirep
=
$self
->prioritized_cb_tofire;
my
$selfArguments
=
$self
->arguments();
my
$cache
=
$self
->hscratchpad(
'_cache'
) || 0;
my
$cacheOptionp
=
$cache
?
$self
->hscratchpad(
'_cacheOption'
) :
undef
;
my
$cacheOptionConditionModep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionConditionMode'
) :
undef
;
my
$cacheOptionConditionp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionCondition'
) :
undef
;
my
$cacheCbDescriptionp
=
$cache
?
$self
->hscratchpad(
'_cacheCbDescription'
) :
undef
;
my
$cacheOptionTopicp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic'
) :
undef
;
my
$cacheOptionTopic_persistencep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic_persistence'
) :
undef
;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
my
$cb
=
$prioritized_cbp
->[
$i
];
my
$option
=
$cache
?
$cacheOptionp
->[
$i
] :
$cb
->option;
my
$conditionMode
=
$cache
?
$cacheOptionConditionModep
->[
$i
] :
$option
->conditionMode;
my
@condition
= ();
my
$description
=
$cache
?
$cacheCbDescriptionp
->[
$i
] :
$cb
->description;
foreach
my
$condition
(
$cache
? @{
$cacheOptionConditionp
->[
$i
]} : @{
$option
->condition}) {
my
(
$coderef
,
@arguments
) = @{
$condition
};
if
(
ref
(
$coderef
) eq
'CODE'
) {
push
(
@condition
,
&$coderef
(
$cb
,
$self
,
$selfArguments
,
@arguments
) ? 1 :0);
}
elsif
(
defined
(
$description
)) {
push
(
@condition
, (
grep
{
$_
eq
$description
} @{
$selfArguments
}) ? 1 :0);
}
}
my
$condition
= 0;
if
(
@condition
) {
$condition
=
shift
(
@condition
);
if
(
$conditionMode
eq
'and'
) {
foreach
(
@condition
) {
$condition
&&=
$_
;
}
}
elsif
(
$conditionMode
eq
'or'
) {
foreach
(
@condition
) {
$condition
||=
$_
;
}
}
}
if
(
$condition
) {
$prioritized_cb_tofirep
->[
$i
] = 1;
foreach
my
$topic
(
keys
%{
$cache
?
$cacheOptionTopicp
->[
$i
] :
$option
->topic}) {
next
if
(!
defined
(
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)));
next
if
(! (
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)));
if
(!
defined
(
$self
->topic_fired(
$topic
))) {
$self
->topic_fired(
$topic
, 1);
$self
->topic_fired_persistence(
$topic
,
$cache
?
$cacheOptionTopic_persistencep
->[
$i
] :
$option
->topic_persistence);
if
(!
defined
(
$self
->topic_fired_data(
$topic
))) {
$self
->topic_fired_data(
$topic
, []);
++
$nbNewTopics
;
}
}
}
++
$nbConditionOK
;
}
else
{
if
(
@condition
) {
$prioritized_cb_tofirep
->[
$i
] = -1;
}
}
}
return
$nbNewTopics
;
}
sub
cache {
my
$self
=
shift
;
my
@cacheOption
= ();
my
@cacheOptionConditionMode
= ();
my
@cacheOptionCondition
= ();
my
@cacheOptionSubscription
= ();
my
@cacheOptionSubscriptionMode
= ();
my
@cacheOptionTopic
= ();
my
@cacheOptionTopic_persistence
= ();
my
@cacheCbDescription
= ();
my
@cacheCbMethod
= ();
my
@cacheCbMethod_void
= ();
my
$prioritized_cbp
=
$self
->prioritized_cb;
my
$ncb
=
$self
->ncb;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
my
$cb
=
$prioritized_cbp
->[
$i
];
my
$option
=
$cb
->option;
push
(
@cacheOption
,
$option
);
push
(
@cacheOptionConditionMode
,
$option
->conditionMode);
push
(
@cacheOptionCondition
,
$option
->condition);
push
(
@cacheOptionSubscription
,
$option
->subscription);
push
(
@cacheOptionSubscriptionMode
,
$option
->subscriptionMode);
push
(
@cacheOptionTopic
,
$option
->topic);
push
(
@cacheOptionTopic_persistence
,
$option
->topic_persistence);
push
(
@cacheCbDescription
,
$cb
->description);
push
(
@cacheCbMethod
,
$cb
->method);
push
(
@cacheCbMethod_void
,
$cb
->method_void);
}
$self
->hscratchpad(
'_cacheOption'
, \
@cacheOption
);
$self
->hscratchpad(
'_cacheOptionConditionMode'
, \
@cacheOptionConditionMode
);
$self
->hscratchpad(
'_cacheOptionCondition'
, \
@cacheOptionCondition
);
$self
->hscratchpad(
'_cacheOptionSubscription'
, \
@cacheOptionSubscription
);
$self
->hscratchpad(
'_cacheOptionSubscriptionMode'
, \
@cacheOptionSubscriptionMode
);
$self
->hscratchpad(
'_cacheOptionTopic'
, \
@cacheOptionTopic
);
$self
->hscratchpad(
'_cacheOptionTopic_persistence'
, \
@cacheOptionTopic_persistence
);
$self
->hscratchpad(
'_cacheCbDescription'
, \
@cacheCbDescription
);
$self
->hscratchpad(
'_cacheCbMethod'
, \
@cacheCbMethod
);
$self
->hscratchpad(
'_cacheCbMethod_void'
, \
@cacheCbMethod_void
);
$self
->hscratchpad(
'_cache'
, 1);
}
sub
_fire {
my
$self
=
shift
;
$self
->firing(1);
my
$ncb
=
$self
->ncb;
my
$prioritized_cb_tofirep
=
$self
->prioritized_cb_tofire;
my
$prioritized_cb_firedp
=
$self
->prioritized_cb_fired;
my
$prioritized_cbp
=
$self
->prioritized_cb;
my
$selfArguments
=
$self
->arguments();
my
$cache
=
$self
->hscratchpad(
'_cache'
) || 0;
my
$cacheCbMethodp
=
$cache
?
$self
->hscratchpad(
'_cacheCbMethod'
) :
undef
;
my
$cacheCbMethod_voidp
=
$cache
?
$self
->hscratchpad(
'_cacheCbMethod_void'
) :
undef
;
my
$cacheOptionTopicp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic'
) :
undef
;
my
$cacheOptionTopic_persistencep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic_persistence'
) :
undef
;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
if
(
$prioritized_cb_tofirep
->[
$i
] <= 0) {
next
;
}
my
$cb
=
$prioritized_cbp
->[
$i
];
if
(
$prioritized_cb_firedp
->[
$i
]) {
next
;
}
$prioritized_cb_firedp
->[
$i
] = 1;
my
$method
=
$cache
?
$cacheCbMethodp
->[
$i
] :
$cb
->method;
if
(
defined
(
$method
)) {
my
@rc
;
if
(
ref
(
$method
) eq
'ARRAY'
) {
my
(
$method
,
@arguments
) = @{
$method
};
if
(
ref
(
$method
) eq
'CODE'
) {
@rc
=
&$method
(
$cb
,
$self
,
$selfArguments
,
@arguments
);
}
else
{
@rc
=
$self
->topic_fired_data(
$cb
->description) || [];
}
}
my
$option
=
$cb
->option;
my
$method_void
=
$cache
?
$cacheCbMethod_voidp
->[
$i
] :
$cb
->method_void;
if
(!
$method_void
) {
foreach
my
$topic
(
keys
%{
$cache
?
$cacheOptionTopicp
->[
$i
] :
$option
->topic}) {
next
if
(!
defined
(
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)));
next
if
((
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)) != 1);
my
$topic_fired_data
=
$self
->topic_fired_data(
$topic
) || [];
if
(
ref
(
$cb
->method) eq
'ARRAY'
) {
if
(
$cb
->method_mode eq
'push'
) {
push
(@{
$topic_fired_data
},
@rc
);
}
else
{
@{
$topic_fired_data
} =
@rc
;
}
}
else
{
if
(
$cb
->method_mode eq
'push'
) {
push
(@{
$topic_fired_data
},
@rc
);
}
else
{
@{
$topic_fired_data
} =
@rc
;
}
}
$self
->topic_fired_data(
$topic
,
$topic_fired_data
);
}
}
}
}
$self
->firing(0);
}
sub
topic_level_fired_data {
my
$self
=
shift
;
my
$topic
=
shift
;
my
$level
=
shift
;
$level
//= 0;
$level
=
int
(
$level
);
if
(
$level
> 0) {
croak
'int(level) must be 0 or a negative number'
;
}
if
(
$level
== 0) {
if
(
@_
) {
$self
->topic_fired_data(
$topic
,
shift
);
}
return
$self
->topic_fired_data(
$topic
);
}
else
{
my
(
$old_topic_fired
,
$old_topic_persistence
,
$old_topic_data
) = @{
$self
->topic_level(
$level
)};
if
(
@_
) {
$old_topic_data
->{
$topic
} =
shift
;
}
return
$old_topic_data
->{
$topic
};
}
}
sub
_inventory_initialize_topic {
my
$self
=
shift
;
my
$keep_topic_fired
= {};
my
$keep_topic_fired_persistence
= {};
my
$keep_topic_fired_data
= {};
foreach
my
$topic
(
keys
%{
$self
->topic_fired}) {
my
$persistence
=
$self
->topic_fired_persistence(
$topic
);
if
(
grep
{
$_
eq
$persistence
}
qw/any level/
) {
$keep_topic_fired
->{
$topic
} =
$self
->topic_fired(
$topic
);
$keep_topic_fired_persistence
->{
$topic
} =
$self
->topic_fired_persistence(
$topic
);
$keep_topic_fired_data
->{
$topic
} =
$self
->topic_fired_data(
$topic
);
}
}
$self
->topic_fired(
$keep_topic_fired
);
$self
->topic_fired_persistence(
$keep_topic_fired_persistence
);
$self
->topic_fired_data(
$keep_topic_fired_data
);
}
sub
_inventory_initialize_tofire {
my
$self
=
shift
;
my
$prioritized_cb_tofirep
=
$self
->prioritized_cb_tofire;
my
$ncb
=
$self
->ncb;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
$prioritized_cb_tofirep
->[
$i
] = 0;
}
}
sub
_inventory_initialize_fired {
my
$self
=
shift
;
my
$prioritized_cb_firedp
=
$self
->prioritized_cb_fired;
my
$ncb
=
$self
->ncb;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
$prioritized_cb_firedp
->[
$i
] = 0;
}
}
sub
_inventory_fire {
my
$self
=
shift
;
$self
->_inventory_initialize_topic();
$self
->_inventory();
}
sub
_inventory {
my
$self
=
shift
;
my
$nbTopicsCreated
= 0;
do
{
$self
->_inventory_initialize_tofire();
$self
->_inventory_initialize_fired();
$nbTopicsCreated
+=
$self
->_inventory_condition_tofire();
$nbTopicsCreated
+=
$self
->_inventory_subscription_tofire();
if
(
$nbTopicsCreated
> 0) {
$self
->_fire();
$nbTopicsCreated
= 0;
}
}
while
(
$nbTopicsCreated
> 0);
}
sub
_inventory_subscription_tofire {
my
$self
=
shift
;
my
$nbNewTopics
= 0;
my
$nbSubscriptionOK
= 0;
my
$ncb
=
$self
->ncb;
my
$prioritized_cbp
=
$self
->prioritized_cb;
my
$prioritized_cb_tofirep
=
$self
->prioritized_cb_tofire;
my
$cache
=
$self
->hscratchpad(
'_cache'
) || 0;
my
$cacheOptionp
=
$cache
?
$self
->hscratchpad(
'_cacheOption'
) :
undef
;
my
$cacheOptionConditionModep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionConditionMode'
) :
undef
;
my
$cacheOptionConditionp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionCondition'
) :
undef
;
my
$cacheCbDescriptionp
=
$cache
?
$self
->hscratchpad(
'_cacheCbDescription'
) :
undef
;
my
$cacheOptionSubscriptionp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionSubscription'
) :
undef
;
my
$cacheOptionSubscriptionModep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionSubscriptionMode'
) :
undef
;
my
$cacheOptionTopicp
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic'
) :
undef
;
my
$cacheOptionTopic_persistencep
=
$cache
?
$self
->hscratchpad(
'_cacheOptionTopic_persistence'
) :
undef
;
foreach
(
my
$i
= 0;
$i
<
$ncb
;
$i
++) {
my
$cb
=
$prioritized_cbp
->[
$i
];
my
$option
=
$cache
?
$cacheOptionp
->[
$i
] :
$cb
->option;
next
if
(
$prioritized_cb_tofirep
->[
$i
] < 0);
my
%subscribed
= ();
my
$nbSubscription
= 0;
foreach
my
$subscription
(
keys
%{
$cache
?
$cacheOptionSubscriptionp
->[
$i
] :
$option
->subscription}) {
next
if
(!
defined
(
$cache
?
$cacheOptionSubscriptionp
->[
$i
]->{
$subscription
} :
$option
->subscription(
$subscription
)));
next
if
(! (
$cache
?
$cacheOptionSubscriptionp
->[
$i
]->{
$subscription
} :
$option
->subscription(
$subscription
)));
++
$nbSubscription
;
if
(
ref
(
$subscription
) eq
'Regexp'
) {
foreach
(
keys
%{
$self
->topic_fired}) {
if
(
$_
=~
$subscription
) {
$subscribed
{
$_
} = 1;
}
}
}
else
{
foreach
(
keys
%{
$self
->topic_fired}) {
if
(
"$_"
eq
"$subscription"
) {
$subscribed
{
$_
} = 1;
}
}
}
}
if
(
$prioritized_cb_tofirep
->[
$i
] == 0 && !
%subscribed
) {
$prioritized_cb_tofirep
->[
$i
] = -2;
next
;
}
if
(
$nbSubscription
> 0 && (
$cache
?
$cacheOptionSubscriptionModep
->[
$i
] :
$option
->subscriptionMode) eq
'required'
&&
$nbSubscription
!=
keys
%subscribed
) {
$prioritized_cb_tofirep
->[
$i
] = -3;
next
;
}
if
(
$prioritized_cb_tofirep
->[
$i
] == 0) {
$prioritized_cb_tofirep
->[
$i
] = 1;
++
$nbSubscriptionOK
;
}
foreach
my
$topic
(
keys
%{
$cache
?
$cacheOptionTopicp
->[
$i
] :
$option
->topic}) {
next
if
(!
defined
(
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)));
next
if
(! (
$cache
?
$cacheOptionTopicp
->[
$i
]->{
$topic
} :
$option
->topic(
$topic
)));
if
(!
defined
(
$self
->topic_fired(
$topic
))) {
$self
->topic_fired(
$topic
, 1);
$self
->topic_fired_persistence(
$topic
,
$option
->topic_persistence);
$self
->topic_fired_data(
$topic
, []);
++
$nbNewTopics
;
}
}
}
return
$nbNewTopics
;
}
sub
currentTopicLevel {
my
$self
=
shift
;
return
scalar
(@{
$self
->topic_level});
}
sub
pushTopicLevel {
my
$self
=
shift
;
push
(@{
$self
->topic_level}, [ dclone(
$self
->topic_fired), dclone(
$self
->topic_fired_persistence),
$self
->topic_fired_data ]);
my
$new_topic_fired
= {};
my
$new_topic_fired_persistence
= {};
my
$new_topic_fired_data
= {};
foreach
my
$topic
(
keys
%{
$self
->topic_fired}) {
my
$persistence
=
$self
->topic_fired_persistence(
$topic
);
if
(
grep
{
$_
eq
$persistence
}
qw/any/
) {
$new_topic_fired
->{
$topic
} =
$self
->topic_fired(
$topic
);
$new_topic_fired_persistence
->{
$topic
} =
$self
->topic_fired_persistence(
$topic
);
$new_topic_fired_data
->{
$topic
} =
$self
->topic_fired_data(
$topic
);
}
}
$self
->topic_fired(
$new_topic_fired
);
$self
->topic_fired_persistence(
$new_topic_fired_persistence
);
$self
->topic_fired_data(
$new_topic_fired_data
);
}
sub
popTopicLevel {
my
$self
=
shift
;
my
(
$old_topic_fired
,
$old_topic_persistence
,
$old_topic_data
) = @{
$self
->topic_level(-1)};
pop
(@{
$self
->topic_level});
$self
->topic_fired(
$old_topic_fired
);
$self
->topic_fired_persistence(
$old_topic_persistence
);
$self
->topic_fired_data(
$old_topic_data
);
}
sub
reset_topic_fired_data {
my
(
$self
,
$topic
,
$value
,
$level
) =
@_
;
$value
//= [];
$level
//= 0;
if
(
ref
(
$value
) ne
'ARRAY'
) {
croak
'Topic fired data must be an ARRAY reference'
;
}
$level
=
int
(
$level
);
if
(
$level
> 0) {
croak
'int(level) must be 0 or a negative number'
;
}
if
(
$level
== 0) {
$self
->topic_fired_data(
$topic
,
$value
);
}
else
{
my
(
$old_topic_fired
,
$old_topic_persistence
,
$old_topic_data
) = @{
$self
->topic_level(
$level
)};
$old_topic_data
->{
$topic
} =
$value
;
}
}
1;