Skip to content

Commit 49f32b5

Browse files
author
Chris White
committed
L1: added entering() leaving() whenever() and POD.
Also some cleanup.
1 parent 44593ee commit 49f32b5

File tree

4 files changed

+198
-33
lines changed

4 files changed

+198
-33
lines changed

lib/XML/Axk/Core.pm

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,19 +322,21 @@ sub run {
322322
#say "Processing $infn";
323323

324324
# Clear the SPs before each file for consistency.
325+
# TODO remove $C and @F from the codebase and samples. They are
326+
# leftovers from before the split of the languages into Ln modules.
325327
$self->{sp}->{'$C'} = undef;
326328
@{$self->{sp}->{'@F'}} = ();
327329

328330
# For now, just process lines rather than XML nodes.
329331
if($infn eq '-') { # stdin
330-
open($fh, '<-') or croak "Can't open stdin!??!!";
332+
open($fh, '<-') or croak "Can't open stdin: $!";
331333
} else { # disk file
332334
open($fh, "<", $infn) or croak "Can't open $infn: $!";
333335
# if $infn is a reference, its contents will be used -
334336
# http://www.perlmonks.org/?node_id=745018
335337
}
336338

337-
$self->run_sax_fh($fh, $infn);
339+
$self->run_sax_fh($fh, $infn); # TODO permit selecting DOM mode
338340

339341
close($fh) or warn "close failed: $!";
340342

@@ -369,13 +371,6 @@ sub new {
369371
options => $hrOpts,
370372

371373
# Load these in the order they are defined in the scripts.
372-
#our @pre_all = ();
373-
#our @pre_file = ();
374-
#our @worklist = ();
375-
#
376-
#our @post_file = ();
377-
#our @post_all = ();
378-
379374
pre_all => [], # List of \& to run before reading the first file
380375
pre_file => [], # List of \& to run before reading each file
381376
worklist => [], # List of [$refCondition, \&action, $when] to be run against each node.

lib/XML/Axk/L1.pm

Lines changed: 187 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ use Scalar::Util qw(reftype);
2121
# Packages we invoke by hand
2222
require XML::Axk::Language;
2323
require Exporter;
24+
25+
# Names the axk script will have access to
2426
our @EXPORT = qw(
2527
pre_all pre_file post_file post_all perform
26-
always never xpath sel on run);
28+
always never xpath sel on run entering leaving whenever);
2729
our @EXPORT_OK = qw( @SP_names );
2830

2931
# Helpers ======================================================== {{{1
@@ -64,36 +66,36 @@ sub post_all :prototype(&) {
6466
# }}}1
6567
# Definers for node actions ====================================== {{{1
6668

67-
## @function public perform (&action[, pattern[, when]])
69+
## @function private add_to_worklist (&action, matcher[, when])
6870
## The main way to define pattern/action pairs. This takes the action first
6971
## since that's how Perl's prototypes are set up the cleanest (block first).
7072
## @params required &action A block to execute when the pattern matches
71-
## @params required pattern The pattern
73+
## @params required matcher An object that defines test(%CPs) to determine
74+
## whether the element described in
75+
## core-parameter hash %CPs matches.
76+
## @params optional when If provided, when to run the action:
77+
## HI, BYE, or CIAO. Default is HI.
7278
sub add_to_worklist {
79+
my $sandbox = _sandbox or croak "Can't find sandbox";
7380
#say "add_to_worklist args: ", Dumper(\@_);
7481
my ($drAction, $refPattern, $when) = @_;
75-
#say "perform(): ", Dumper(\@_);
7682
$when = $when // HI; # only on entry, by default
77-
7883
$refPattern = \( my $temp = $refPattern ) unless ref($refPattern);
7984

80-
# TODO? support Regexp, scalar patterns in some sensible way
81-
82-
my $sandbox = _sandbox or croak("Can't find sandbox in perform");
8385
push @{$sandbox->worklist}, [$refPattern, $drAction, $when];
84-
} #perform()
86+
} #add_to_worklist()
8587

8688
# User-facing alias for add_to_worklist
8789
sub perform :prototype(&@) {
8890
goto &add_to_worklist; # Need goto so that _sandbox() can use caller(1)
8991
}
9092

91-
# run { action } [optional <when>] - syntactic sugar for sub {}, when
93+
# run { action } [optional <when>] - syntactic sugar for `sub {}, when`
9294
sub run :prototype(&;$) {
9395
return @_;
9496
} #run()
9597

96-
# pattern-first style - on {} run {} [when];
98+
# pattern-first style - on {} run {} [when (default HI)];
9799
sub on :prototype(&@) {
98100
my ($drMakeMatcher, $drAction, $when) = @_;
99101

@@ -105,6 +107,32 @@ sub on :prototype(&@) {
105107
goto &add_to_worklist;
106108
} # on()
107109

110+
# pattern-first style, sugar for symmetry with whenever() and leaving()
111+
sub entering :prototype(&@) {
112+
goto &on
113+
} #entering()
114+
115+
# pattern-first style, common implementation for BYE and CIAO
116+
sub _leaving_whenever_impl {
117+
croak "Too many arguments" if $#_>2;
118+
my ($when, $drMakeMatcher, $drAction) = @_;
119+
my $matcher = &$drMakeMatcher;
120+
@_=($drAction, $matcher, $when);
121+
goto &add_to_worklist;
122+
} #_leaving_whenever_impl()
123+
124+
# pattern-first style, specific to leaving nodes (BYE) - leaving {} run {};
125+
sub leaving :prototype(&@) {
126+
unshift @_, BYE;
127+
goto &_leaving_whenever_impl;
128+
} # leaving()
129+
130+
# pattern-first style, specific to hitting nodes (CIAO) - whenever {} run {};
131+
sub whenever :prototype(&@) {
132+
unshift @_, CIAO;
133+
goto &_leaving_whenever_impl;
134+
} # whenever()
135+
108136
# }}}1
109137
# Definers for matchers ========================================== {{{1
110138

@@ -158,14 +186,13 @@ our @SP_names = qw($C @F $D $E $NOW);
158186

159187
sub update {
160188
#say "L1::update: ", Dumper(\@_);
161-
my $hrSP = shift or croak("No hrSP");
162-
my %opts = @_;
163-
164-
$hrSP->{'$D'} = $opts{document} or croak("No document");
165-
$hrSP->{'$E'} = $opts{record} or croak("No record");
166-
croak("You are in a timeless maze") unless defined $opts{NOW};
167-
$hrSP->{'$NOW'} = now_names $opts{NOW};
168-
#while (my ($key, $value) = each %new_sps) { }
189+
my $hrSP = shift or croak("Invalid call - No hrSP");
190+
my %CPs = @_;
191+
192+
$hrSP->{'$D'} = $CPs{document} or croak("No document");
193+
$hrSP->{'$E'} = $CPs{record} or croak("No record");
194+
croak("You are in a timeless maze") unless defined $CPs{NOW};
195+
$hrSP->{'$NOW'} = now_names $CPs{NOW};
169196
} #update()
170197

171198
# }}}1
@@ -174,19 +201,157 @@ sub update {
174201

175202
sub import {
176203
#say "update: ",ref \&update, Dumper(\&update);
177-
my $target = caller;
178204
#say "XAL1 run from $target:\n", Devel::StackTrace->new->as_string;
179205
XML::Axk::Language->import(
180-
target => $target,
206+
target => caller,
181207
sp => \@SP_names,
182208
updater => \&update
183209
);
184210
# By doing this here rather than in the `use` statement,
185-
# we get $target and don't have to walk the stack to find the
211+
# we get `caller` and don't have to walk the stack to find the
186212
# axk script.
187213
goto &Exporter::import; # for @EXPORT &c. @_ is what it was on entry.
188214
} #import()
189215

190216
#}}}1
191217
1;
218+
# === Documentation ===================================================== {{{1
219+
220+
=pod
221+
222+
=encoding UTF-8
223+
224+
=head1 NAME
225+
226+
XML::Axk::Core::L1 - ack-like XML processor, language 1
227+
228+
=head1 EXAMPLE
229+
230+
L1
231+
on { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName}, CIAO
232+
# "CIAO" can also be "HI" or "BYE" (default HI).
233+
# "entering" is a synonym for "on" with no HI/BYE/CIAO.
234+
whenever { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName};
235+
# the same as the "on ... CIAO" line
236+
leaving { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName};
237+
238+
=head1 PATTERNS AND ACTIONS
239+
240+
=head2 C<< on {<matcher>} run {<action>} [, <when>] >>
241+
242+
Whenever C<< <matcher> >> says that a node matches, run C<< <action> >>.
243+
The optional C<< <when> >> parameter says when in the course of processing to
244+
run C<< <action> >>:
245+
246+
=over
247+
248+
=item C<HI>
249+
250+
When the node is first reached, before any of its children are processed
251+
252+
=item C<BYE>
253+
254+
After all of the node's children have been processed.
255+
256+
=item C<CIAO>
257+
258+
Both C<HI> and C<BYE>. Suggestions for alternative terminology are welcome.
259+
260+
=back
261+
262+
=head2 C<entering>, C<whenever> C<leaving>
263+
264+
entering {<matcher>} run {<action>}
265+
whenever {<matcher>} run {<action>}
266+
leaving {<matcher>} run {<action>}
267+
268+
The same as C<on {} run {}>, with C<when> set to C<HI>, C<CIAO>, or C<BYE>
269+
respectively.
270+
271+
=head2 C<< perform { <action> } <matcher> [, <when>] >>
272+
273+
If you prefer RPN, or you want to save some characters, you can put the
274+
C<< <matcher> >> after the C<< <action> >> using C<perform>. For example,
275+
the following two lines have exactly the same effect:
276+
277+
on { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName}, CIAO
278+
perform {say "$NOW: " . $E->getTagName} xpath(q<//item>), CIAO
279+
280+
=head1 VARIABLES
281+
282+
When an C<< <action> >> is running, it has access to predefined variables
283+
that hold the state of the element being matched. This is similar to C<$0>,
284+
C<$1>, ... in awk.
285+
286+
=over
287+
288+
=item B<$D>
289+
290+
The current XML document
291+
292+
=item B<$E>
293+
294+
The XML element that was matched
295+
296+
=item B<$NOW>
297+
298+
The current phase, as a human-readable string: C<entering> for C<HI>,
299+
C<leaving> for C<BYE>, and C<BOTH> for C<CIAO>.
300+
301+
=back
302+
303+
=head1 MATCHERS
304+
305+
=head2 C<< xpath('xpath expression') >>
306+
307+
Match nodes that match the given XPath expression. Remember that Perl will
308+
interpolate C<@name> in double-quotes, so single-quote or C<q{}> your XPath
309+
expressions.
310+
311+
=head2 C<< sel('selector') >>
312+
313+
Match nodes that match the given selector.
314+
315+
=head2 C<always>, C<never>
316+
317+
Always or never match, respectively.
318+
319+
=head1 SPECIAL ACTIONS
320+
321+
=head2 C<< pre_all {<block>} >>
322+
323+
Run C<< <block> >> before any file is processed.
324+
325+
=head2 C<< pre_file {<block>} >>
326+
327+
Run C<< <block>($filename) >> before each file is processed.
328+
329+
=head2 C<< post_file {<block>} >>
330+
331+
Run C<< <block>($filename) >> after each file is processed.
332+
333+
=head2 C<< post_all {<block>} >>
334+
335+
Run C<< <block> >> after all files have been processed.
336+
337+
=head1 AUTHOR
338+
339+
Christopher White, C<cxwembedded at gmail.com>
340+
341+
=head1 CONTACT
342+
343+
For any bug reports, feature requests, or questions, please see the
344+
information in L<XML::Axk>.
345+
346+
=head1 LICENSE AND COPYRIGHT
347+
348+
Copyright (c) 2018 Christopher White. All rights reserved.
349+
350+
This program is free software; you can redistribute it and/or modify it
351+
under the terms of the the Artistic License (2.0). Details are in the LICENSE
352+
file accompanying this distribution.
353+
354+
=cut
355+
356+
# }}}1
192357
# vi: set ts=4 sts=4 sw=4 et ai fo-=ro foldmethod=marker: #

run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22
if [[ $# -eq 0 ]]; then
3-
args=(-f t/ex/xml1.axk t/ex1.xml)
3+
args=(-f t/ex/xml1.axk t/ex/ex1.xml)
44
else
55
args=("$@")
66
fi

t/ex/xml1.axk

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
L1
22

3-
perform { say "** This shouldn't have happened" } never;
3+
pre_all { say "pre_all" };
4+
pre_file { say "pre_file" };
5+
post_all { say "post_all" };
6+
post_file { say "post_file" };
7+
8+
perform { die "** This shouldn't have happened" } never;
49

510
perform { say "hi ", $E->getTagName if $E->can("getTagName"); } always, HI;
611
perform { say "bye ", $E->getTagName if $E->can("getTagName"); } always, BYE;

0 commit comments

Comments
 (0)