File Coverage

blib/lib/YAMLScript/Reader.pm
Criterion Covered Total %
statement 660 855 77.1
branch 161 278 57.9
condition 65 110 59.0
subroutine 113 146 77.4
pod 0 118 0.0
total 999 1507 66.2


line stmt bran cond sub pod time code
1 11     11   83 use strict; use warnings;
  11     11   22  
  11         307  
  11         55  
  11         21  
  11         424  
2              
3             # Needed for Perl versions < 5.20
4             BEGIN {
5 11 50   11   695 warnings->unimport('experimental::signatures')
  11     11   70  
  11         25  
  11         457  
6             if eval "use warnings 'experimental::signatures'; 1";
7             }
8 11     11   1792 use feature 'signatures';
  11         21  
  11         14402  
9              
10             package YAMLScript::Reader;
11              
12 11     11   80 use YAMLScript::Common;
  11         19  
  11         2327  
13 11     11   4994 use Lingy::Reader;
  11         40735  
  11         394  
14              
15 11     11   75 use base 'Lingy::Reader';
  11         22  
  11         1411  
16              
17 11     11   6297 use Regexp::Common;
  11         54705  
  11         40  
18 11     11   1787137 use Scalar::Util 'refaddr';
  11         31  
  11         119511  
19              
20             our %events;
21             our %functions;
22             our %refs;
23              
24             my $main_called = 0;
25              
26             our $read_ys = 0;
27              
28             #------------------------------------------------------------------------------
29             # Convert YAMLScript into a Lingy AST
30             #------------------------------------------------------------------------------
31 32     32 0 487812 sub new { bless {}, shift }
32              
33             sub read_str {
34 79     79 0 13771 my $self = shift;
35 79         209 my ($str) = @_;
36 79 100       268 if ($read_ys) {
37 7         20 return $self->read_ys($str);
38             } else {
39 72         364 return $self->SUPER::read_str(@_);
40             }
41             }
42              
43             sub read_ys {
44 58     58 0 159 my ($self, $yaml, $file) = (@_, '');
45              
46 58         212 $self->{yaml} = $yaml;
47 58         116 $self->{file} = $file;
48              
49 58         388 %events = ();
50 58         100 %functions = ();
51 58         99 %refs = ();
52              
53 58         150 $self->{events} = $self->parse_yaml_pp($yaml);
54 58         177 my $dom = $self->compose_dom;
55 57 50       238 my $ast = $file
56             ? $self->construct_ast($dom)
57             : $self->construct_expr($dom);
58              
59 57         402 return $ast;
60             }
61              
62             our @event_keys = (qw<
63             type
64             bpos blin bcol
65             epos elin ecol
66             anch ytag
67             styl valu
68             >);
69              
70             sub parse_yaml_fy {
71 0     0 0 0 my ($self, $yaml) = @_;
72              
73 0         0 require IPC::Run;
74              
75 0         0 my ($out, $err);
76 0         0 IPC::Run::run(
77             [qw< fy-tool --testsuite --tsv-format >],
78             $yaml,
79             \$out,
80             \$err,
81             IPC::Run::timeout(5),
82             );
83              
84 0         0 [ map 'event'->new($_), split /\n/, $out ];
85             }
86              
87             my $event_dict = {
88             stream_start_event => '+str',
89             stream_end_event => '-str',
90             document_start_event => '+doc',
91             document_end_event => '-doc',
92             mapping_start_event => '+map',
93             mapping_end_event => '-map',
94             sequence_start_event => '+seq',
95             sequence_end_event => '-seq',
96             scalar_event => '=val',
97             alias_event => '=ali',
98             };
99              
100             sub parse_yaml_pp {
101 58     58 0 119 my ($self, $yaml) = @_;
102 58         355 require YAML::PP::Parser;
103 58         177 my $events = [];
104             YAML::PP::Parser->new(
105             receiver => sub {
106 539     539   103731 my ($self, undef, $event) = @_;
107             my @event = (
108             ($event_dict->{$event->{name}} || XXX($event)),
109             0, 0, 0, 0, 0, 0,
110             ($event->{anchor} || '-'),
111 539   33     3418 ($event->{tag} || '-'),
      50        
      50        
112             );
113 539 100       1263 if ($event->{name} eq 'scalar_event') {
114 149         276 my $value = $event->{value};
115 149         253 my $style = $event->{style};
116 149         327 $value =~ s/\\/\\\\/g;
117 149         219 $value =~ s/\n/\\n/g;
118 149 50       459 push @event,
    100          
119             (
120             $style == 1 ? ':' :
121             $style == 4 ? '|' :
122             '"'
123             ),
124             $value;
125             }
126 539         2845 push @$events, join "\t", @event;
127             },
128 58         465 )->parse_string($yaml);
129 58         1261 [ map 'event'->new($_), @$events ];
130             }
131              
132             #------------------------------------------------------------------------------
133             # AST Implicit Typing Methods
134             #------------------------------------------------------------------------------
135              
136             my $bp = $RE{balanced}{-parens=>'()'};
137             my $bs = $RE{balanced}{-parens=>'[]'};
138              
139             my $E_GROUP = 'event'->new("=xxx\t-1\t-1\t-1\t-1\t-1\t-1\t-\t-\t-\t-");
140             my $E_PLAIN = 'event'->new("=xxx\t-1\t-1\t-1\t-1\t-1\t-1\t-\t-\t:\t-");
141             my $E_QUOTE = 'event'->new("=xxx\t-1\t-1\t-1\t-1\t-1\t-1\t-\t-\t'\t-");
142 0     0 0 0 sub PAIR { 'pair'->new(@_) }
143 0     0 0 0 sub MAP { 'map'->new($E_GROUP, @_) }
144 35     35 0 114 sub SEQ { 'seq'->new($E_GROUP, @_) }
145 0     0 0 0 sub VAL { 'val'->new($E_PLAIN, @_) }
146 0     0 0 0 sub STR { 'val'->new($E_QUOTE, @_) }
147              
148 0     0 0 0 sub B { BOOLEAN->new($_[0]) }
149 0     0 0 0 sub K { KEYWORD->new(@_) }
150 105     105 0 712 sub L { LIST->new([@_]) }
151 20     20 0 84 sub N { NUMBER->new($_[0]) }
152 138     138 0 455 sub S { SYMBOL->new($_[0]) }
153 2     2 0 22 sub T { STRING->new($_[0]) }
154 14     14 0 81 sub V { VECTOR->new([@_]) }
155              
156 17     17 0 40 sub DEF { S 'def' }
157 6     6 0 26 sub DO { S 'do' }
158 14     14 0 83 sub FN { S 'fn*' }
159 0     0 0 0 sub IF { S 'if' }
160 0     0 0 0 sub LET { S 'let*' }
161              
162             my $sym = qr<(?:
163             [-:.]?
164             \w+
165             (?:
166             (?:[-+./]|::)
167             \w+
168             )*
169             [\?\!\*]?
170             )>x;
171              
172 0     0 0 0 sub error($m) { die "YS Error: $m\n" }
  0         0  
  0         0  
  0         0  
173 368     368 0 448 sub event($n) { $events{refaddr($n)} }
  368         440  
  368         422  
  368         1883  
174 281     281 0 355 sub e_style($n) { event($n)->{styl} }
  281         368  
  281         378  
  281         415  
175 87     87 0 124 sub e_tag($n) { event($n)->{ytag} }
  87         123  
  87         112  
  87         158  
176 531     531 0 674 sub is_map($n) { ref($n) eq 'map' }
  531         699  
  531         634  
  531         1495  
177 234     234 0 335 sub is_seq($n) { ref($n) eq 'seq' }
  234         320  
  234         283  
  234         630  
178 328     328 0 385 sub is_val($n) { ref($n) eq 'val' }
  328         408  
  328         400  
  328         1014  
179 292     292 0 366 sub is_pair($n) { ref($n) eq 'pair' }
  292         366  
  292         357  
  292         848  
180 85     85 0 120 sub is_key($n) { $n->{xkey} }
  85         242  
  85         118  
  85         257  
181 121 100   121 0 182 sub is_plain($n) { is_val($n) and e_style($n) eq ':' }
  121         165  
  121         148  
  121         208  
182 87 50   87 0 135 sub is_double($n) { is_val($n) and e_style($n) eq '"' }
  87         119  
  87         123  
  87         157  
183 85 50   85 0 138 sub is_literal($n) { is_val($n) and e_style($n) eq '|' }
  85         124  
  85         109  
  85         149  
184 0     0 0 0 sub is_single($n) {
  0         0  
  0         0  
185 0 0 0     0 return unless is_map($n) and pairs($n) == 1;
186 0         0 @{$n->{pair}[0]};
  0         0  
187             }
188 0     0 0 0 sub is_assign($n) {
  0         0  
  0         0  
189 0 0       0 is_single($n) and
190             text(key(first_pair($n))) =~ /^$sym\s+=$/;
191             }
192 14 100   14 0 19 sub is_def($n) { is_map($n) and tag(key(first_pair($n))) eq 'def' }
  14         22  
  14         21  
  14         26  
193              
194 171 50   171 0 213 sub assert_map($n) { is_map($n) or ZZZ($n) }
  171         240  
  171         215  
  171         270  
195 72 50   72 0 92 sub assert_seq($n) { is_seq($n) or ZZZ($n) }
  72         89  
  72         91  
  72         120  
196 35 50   35 0 51 sub assert_val($n) { is_val($n) or ZZZ($n) }
  35         49  
  35         49  
  35         69  
197 57 50   57 0 80 sub assert_pair($n) { is_pair($n) or ZZZ($n) }
  57         76  
  57         70  
  57         112  
198 12 50   12 0 19 sub assert_elems($n) { assert_seq($n); @{$n->elem} > 0 or ZZZ($n) }
  12         17  
  12         16  
  12         30  
  12         20  
  12         30  
199 2 50   2 0 3 sub assert_pairs($n) { assert_map($n); @{$n->pair} > 0 or ZZZ($n) }
  2         3  
  2         4  
  2         7  
  2         4  
  2         4  
200 169     169 0 217 sub pairs($n) { assert_map($n); @{$n->pair} }
  169         217  
  169         360  
  169         372  
  169         392  
  169         352  
201 60     60 0 247 sub elems($n) { assert_seq($n); @{$n->elem} }
  60         92  
  60         76  
  60         136  
  60         93  
  60         142  
202 237     237 0 343 sub tag($n) { $n->{ytag} }
  237         319  
  237         277  
  237         452  
203 57     57 0 80 sub key($p) { assert_pair($p); $p->key }
  57         77  
  57         74  
  57         133  
  57         143  
204 0     0 0 0 sub val($p) { assert_pair($p); $p->val }
  0         0  
  0         0  
  0         0  
  0         0  
205 0     0 0 0 sub key_val($p) { assert_pair($p); @$p }
  0         0  
  0         0  
  0         0  
  0         0  
206 35     35 0 56 sub text($v) { assert_val($v); $v->{text} }
  35         53  
  35         47  
  35         84  
  35         666  
207 12     12 0 18 sub first_elem($n) { assert_elems($n); (elems($n))[0] }
  12         16  
  12         18  
  12         33  
  12         41  
208 2     2 0 4 sub first_pair($n) { assert_pairs($n); (pairs($n))[0] }
  2         4  
  2         4  
  2         7  
  2         4  
209              
210 57     57 0 88 sub construct_expr($s, $n) {
  57         84  
  57         75  
  57         84  
211 57         153 my @ast = $s->construct($n);
212              
213 57 100       207 @ast == 1
214             ? $ast[0]
215             : L(DO, @ast);
216             }
217              
218 0     0 0 0 sub construct_ast($s, $n) {
  0         0  
  0         0  
  0         0  
219 0         0 my $ast = $s->construct_expr($n);
220              
221 0 0       0 if (need_main_call($ast)) {
222 0         0 $ast = L(
223             DO,
224             $ast,
225             L(
226             S('apply'),
227             S('main'),
228             S('*command-line-args*'),
229             ),
230              
231             );
232             }
233              
234 0         0 return $ast;
235             }
236              
237 235     235 0 518 sub construct($s, $n) {
  235         380  
  235         319  
  235         297  
238 235 100       399 my $tag = is_pair($n) ? tag(key($n)) : tag($n);
239 235 50       496 XXX $n, "No tag for node" unless $tag;
240 235         453 my $constructor = "construct_$tag";
241 235         730 $s->$constructor($n);
242             }
243              
244 0     0 0 0 sub construct_boolean($s, $n) {
  0         0  
  0         0  
  0         0  
245 0 0       0 "$n" eq 'true' ? true :
    0          
246             "$n" eq 'false' ? false :
247             die;
248             }
249              
250 34     34 0 45 sub construct_call($s, $p) {
  34         60  
  34         46  
  34         43  
251 34         75 my ($k, $v) = @$p;
252 34 50       67 "$k" =~ /^($sym)($bp?)$/ or die;
253 34         3838 my $fn = $1;
254 34         76 my $args = $2; # TODO add these args to value args
255 34 100       73 if ($args) {
256 11 50       60 $args =~ s/^\((.*)\)$/$1/ or die;
257             }
258 34         78 $fn =~ s/^(let|try|catch)$/$1*/;
259 34 50       80 $main_called = 1 if $fn eq 'main';
260 34         105 $args = 'val'->new(undef, $args);
261 34         85 $args->{ytag} = 'ysexpr';
262 34 100       78 $v = SEQ($v) unless is_seq($v);
263 34         85 L(S($fn), map $s->construct($_), $args, elems($v));
264             }
265              
266 5     5 0 18 sub construct_def($s, $p) {
  5         11  
  5         10  
  5         9  
267 5         14 my ($k, $v) = @$p;
268 5 50       15 "$k" =~ /^($sym)\s*=$/ or die;
269 5         35 my $sym = S($1);
270 5         68 my $rhs = $s->construct($v);
271 5         46 return L(DEF, $sym, $rhs);
272             }
273              
274             sub get_sig {
275 12     12 0 23 my ($sig) = @_;
276 12         23 my $args = [];
277 12         20 my $dargs = [];
278 12         422 while ($sig =~ s/^($sym)(?=,?\s|$),?\s*//) {
279 16         148 push @$args, symbol($1);
280             }
281 12 100       304 if ($sig =~ s/^\*($sym)//) {
282 3         12 push @$args, symbol('&'), symbol($1);
283             }
284             else {
285 9 100       212 if ($sig =~ /^($sym)=/) {
286 1         6 push @$args, symbol('&'), symbol('_args_');
287             }
288 9         246 while ($sig =~ s/^($sym)=(\S+),?\s*//) {
289 1         4 my ($s, $x) = ($1, $2);
290 1         4 push @$dargs, $1;
291 1         7 push @$dargs, read_ysexpr($x);
292             }
293             }
294 12 50       71 err "Can't parse function signature '$_[0]'"
295             if length($sig);
296 12         36 return ($args, $dargs);
297             }
298              
299 6     6 0 15 sub construct_defn($s, $p) {
  6         9  
  6         11  
  6         8  
300 6         14 my ($k, $v) = @$p;
301 6         19 my ($def, $name, $args, $body) = $s->_defn_parse($k, $v, 0);
302 6         28 return L($def, $name, V(@$args), @$body);
303             }
304              
305 1     1 0 2 sub construct_defn_multi($s, $p) {
  1         1  
  1         2  
  1         2  
306 1         3 my ($k, $v) = @$p;
307 1 50       4 text($k) =~ /^(defn|defmacro)\s+($sym)$/ or die;
308 1         6 my $def = $1;
309 1         3 my $name = S($2);
310             my @defs = map {
311 1         10 my ($k, $v) = @$_;
  4         20  
312 4         23 my (undef, undef, $args, $body) = $s->_defn_parse($k, $v, 1);
313 4         13 L(V(@$args), @$body);
314             } pairs($v);
315 1         12 return L($def, $name, @defs);
316             }
317              
318 2     2 0 5 sub construct_fn($s, $p) {
  2         3  
  2         2  
  2         3  
319 2         5 my ($k, $v) = @$p;
320 2         7 my ($def, $name, $args, $body) = $s->_defn_parse($k, $v, 0);
321 2         9 return L(FN, V(@$args), @$body);
322             }
323              
324 12     12   23 sub _defn_parse($s, $k, $v, $m) {
  12         16  
  12         20  
  12         17  
  12         17  
  12         18  
325 12         22 my ($def, $name, $sig);
326 12 100       28 if ($m) {
327 4 50       8 text($k) =~ /^($sym?)?\((.*)\)$/ or XXX $k;
328 4         14 $def = '';
329 4         10 $name = S($1);
330 4         32 $sig = $2;
331             } else {
332 8 50       25 text($k) =~ /^(fn|defn|defmacro)\s+($sym?)?\((.*)\)$/ or XXX $k;
333 8         34 $def = S($1);
334 8         80 $name = S($2);
335 8         55 $sig = $3;
336             }
337 12         30 my ($args, $dargs) = get_sig($sig);
338 12         31 my $defn = L( DEF, $name, L( FN, L, nil ) );
339 12 100       67 my $seq = is_seq($v) ? $v : SEQ($v);
340 12         40 my $first = first_elem($seq);
341             my $body = [
342             (@$dargs or is_def($first) or is_map($first))
343             ? ($s->construct_let($seq, $args, $dargs))
344 12 100 66     61 : map $s->construct($_), @{$seq->elem},
  10         21  
345             ];
346 12         107 return $def, $name, $args, $body;
347             }
348              
349 6     6 0 13 sub construct_do($s, $n) {
  6         10  
  6         13  
  6         12  
350 6         18 my @elems = elems($n);
351 6 100       27 if (@elems == 1) {
352 3         15 $s->construct($elems[0]);
353             } else {
354 3         25 L(
355             DO,
356             map $s->construct($_), @elems,
357             );
358             }
359             }
360              
361 2     2 0 5 sub construct_if($s, $p) {
  2         23  
  2         6  
  2         4  
362 2         6 my ($k, $v) = @$p;
363 2 50       5 "$k" =~ /^if +($bp)/ or die;
364 2         278 my $cond = read_ysexpr($1);
365 2 50       8 my @elems = is_seq($v) ? elems($v) : $v;
366 2         8 L(
367             S('if'),
368             $cond,
369             map $s->construct($_), @elems,
370             );
371             }
372              
373 19     19 0 36 sub construct_int($s, $n) { N("$n") }
  19         28  
  19         27  
  19         28  
  19         43  
374              
375 0     0 0 0 sub construct_istr($s, $n) {
  0         0  
  0         0  
  0         0  
376 0         0 my @list;
377 0         0 local $_ = "$n";
378 0         0 while (length) {
379 0 0       0 if (s/\A\$($sym)//) {
    0          
    0          
380 0         0 push @list, S($1);
381             } elsif (s/\A\$($bp)//s) {
382 0         0 push @list, read_ysexpr($1);
383             } elsif (s/\A(.+?)(?=\$)//s) {
384 0         0 push @list, T($1);
385             } else {
386 0         0 push @list, T($_);
387 0         0 $_ = '';
388             }
389             }
390 0         0 L(S('str'), @list);
391             }
392              
393 0     0 0 0 sub construct_keyword($s, $n) {
  0         0  
  0         0  
  0         0  
394 0         0 K("$n");
395             }
396              
397 2     2 0 7 sub construct_let($s, $n, $a, $d) {
  2         4  
  2         3  
  2         3  
  2         3  
  2         3  
398 2         5 my @elems = elems($n);
399 2 50 66     7 if (is_map($elems[0]) and @{$elems[0]->{pair}} > 1) {
  1         5  
400 0         0 my $elem = shift @elems;
401 0         0 for my $pair (reverse @{$elem->{pair}}) {
  0         0  
402 0         0 unshift @elems, bless {
403             pair => [$pair],
404             ytag => 'module',
405             }, 'map';
406             }
407             }
408 2         4 my @defs;
409 2         9 my $i = 0;
410 2         7 while (@$d) {
411 1         6 my ($sym, $form) = splice(@$d, 0, 2);
412 1         3 push @defs, S($sym), L(S('nth'), S('_args_'), N($i), $form);
413 1         7 $i++;
414             }
415 2   66     14 while (@elems and is_def($elems[0])) {
416 1         9 my $d = shift @elems;
417 1         4 my ($p) = pairs($d);
418 1         4 my ($k, $v) = @$p;
419 1 50       3 (my $sym = "$k") =~ s/\s+=$// or die;
420 1         5 push @defs, S($sym), $s->construct($v);
421             }
422             L(
423 2         7 S('let*'),
424             V(@defs),
425             map $s->construct($_), @elems,
426             );
427             }
428              
429 0     0 0 0 sub construct_let1($s, $n) {
  0         0  
  0         0  
  0         0  
430 0         0 my @elems = elems($n->[1]);
431 0 0       0 my $assigns = shift @elems or die;
432 0         0 my $defs = [];
433 0 0       0 if (is_map($assigns)) {
    0          
434 0         0 for my $pair (pairs($assigns)) {
435 0         0 my ($k, $v) = @$pair;
436 0         0 $k = "$k";
437 0 0       0 $k =~ s/\ +=$// or die;
438 0         0 push @$defs, S($k);
439 0         0 push @$defs, $s->construct($v);
440             }
441             } elsif (is_seq($assigns)) {
442 0         0 XXX $n;
443             } else {
444 0         0 XXX $n;
445             }
446              
447 0         0 L(
448             S('let*'),
449             $defs,
450             map $s->construct($_), @elems,
451             );
452             }
453              
454 1     1 0 2 sub construct_loop($s, $p) {
  1         2  
  1         2  
  1         2  
455 1         3 my ($k, $v) = @$p;
456 1 50       3 "$k" =~ /^loop +($bs)/ or die;
457 1         212 my $bindings = read_ysexpr($1);
458 1 50       4 my @elems = is_seq($v) ? elems($v) : $v;
459 1         4 L(
460             S('loop'),
461             $bindings,
462             map $s->construct($_), @elems,
463             );
464             }
465              
466 53     53 0 84 sub construct_module($s, $n) {
  53         67  
  53         84  
  53         66  
467 53         110 my @forms = map $s->construct($_), pairs($n);
468 53 100       585 return $forms[0] if @forms == 1;
469 2         10 L(DO, @forms);
470             }
471              
472 2     2 0 6 sub construct_str($s, $n) {
  2         4  
  2         4  
  2         4  
473 2         7 T("$n");
474             }
475              
476 27     27 0 45 sub construct_sym($s, $n) {
  27         38  
  27         34  
  27         39  
477 27         56 S("$n");
478             }
479              
480 0     0 0 0 sub construct_try($s, $p) {
  0         0  
  0         0  
  0         0  
481             L(
482             S('try*'),
483             map $s->construct($_),
484             map {
485 0 0       0 is_map($_) ? first_pair($_) : $_
  0         0  
486             } elems(val($p)),
487             );
488             }
489              
490 0     0 0 0 sub construct_catch($s, $p) {
  0         0  
  0         0  
  0         0  
491 0 0       0 key($p) =~ /^catch\(($sym)\)$/ or die;
492 0         0 L(
493             S('catch*'),
494             S($1),
495             $s->construct(val($p)),
496             );
497             }
498              
499 1     1 0 4 sub construct_use($s, $p) {
  1         2  
  1         3  
  1         1  
500 1         3 my ($k, $v) = @$p;
501 1         4 $v = $s->construct($v);
502 1 50       10 if (ref($v) eq SYMBOL) {
503 1         3 $v = L(S('quote'), $v);
504             }
505 1         6 L(S("$k"), $v);
506             }
507              
508 0     0 0 0 sub construct_val($s, $n) {
  0         0  
  0         0  
  0         0  
509 0         0 T("$n");
510             }
511              
512 3     3 0 4 sub construct_when($s, $p) {
  3         6  
  3         4  
  3         4  
513 3         7 my ($k, $v) = @$p;
514 3 50       6 (my $expr = "$k") =~ s/ ?([?|])$// or die;
515 3 100       14 my $fn = $1 eq '?' ? 'when' : 'when-not';
516 3         9 my $cond = read_ysexpr($expr);
517 3 50       7 my @elems = is_seq($v) ? elems($v) : $v;
518 3         7 L(
519             S($fn),
520             $cond,
521             map $s->construct($_), @elems,
522             );
523             }
524              
525 0     0 0 0 sub construct_yamlscript($s, $n) {
  0         0  
  0         0  
  0         0  
526 0         0 my @forms = map $s->construct($_), pairs($n);
527 0 0       0 return $forms[0] if @forms == 1;
528 0         0 L(DO, @forms);
529             }
530              
531 73     73 0 103 sub construct_ysexpr($s, $n) {
  73         99  
  73         100  
  73         121  
532 73         146 read_ysexpr($n);
533             }
534              
535             # Plain YAML data constructors:
536 0     0 0 0 sub construct_map($s, $n) {
  0         0  
  0         0  
  0         0  
537 0         0 my $map = [];
538 0         0 for my $p (pairs($n)) {
539 0         0 my ($k, $v) = @$p;
540 0 0       0 is_val($k) or XXX $k, "!map keys must be strings";
541 0         0 push @$map, STRING->new("$k");
542 0         0 push @$map, $s->construct_value($v);
543             }
544 0         0 HASHMAP->new($map);
545             }
546              
547 0     0 0 0 sub construct_seq($s, $n) {
  0         0  
  0         0  
  0         0  
548 0         0 my $seq = [];
549 0         0 for my $v (elems($n)) {
550 0         0 push @$seq, $s->construct_value($v);
551             }
552 0         0 VECTOR->new($seq);
553             }
554              
555 0     0 0 0 sub construct_value($s, $v) {
  0         0  
  0         0  
  0         0  
556 0         0 my $t = ref($v);
557 0 0       0 if ($t eq 'val') {
    0          
    0          
558 0         0 my $s = e_style($v);
559 0 0       0 if ($s eq ':') {
560             return
561 0 0       0 ($v =~ /^-?\d+(\.d+)?$/) ? NUMBER->new("$v") :
    0          
    0          
    0          
562             ("$v" eq 'true') ? true :
563             ("$v" eq 'false') ? false :
564             ("$v" eq 'null') ? nil :
565             STRING->new("$v");
566             } else {
567 0         0 return STRING->new("$v");
568             }
569             }
570             elsif ($t eq 'map') {
571 0         0 $v->{ytag} = 'map';
572 0         0 return $s->construct_map($v);
573             }
574             elsif ($t eq 'seq') {
575 0         0 $v->{ytag} = 'seq';
576 0         0 return $s->construct_seq($v);
577             }
578             else {
579 0         0 XXX $v, "Don't know how to contruct this";;
580             }
581             }
582              
583 0     0 0 0 sub is_main($n) {
  0         0  
  0         0  
584 0 0 0     0 ref($n) eq LIST and
      0        
      0        
      0        
      0        
585             @$n >= 2 and
586             ref($n->[0]) eq SYMBOL and
587             "$n->[0]" eq 'defn' and
588             ref($n->[1]) eq SYMBOL and
589             "$n->[1]" eq 'main' and
590             1;
591             }
592              
593 0     0 0 0 sub need_main_call($ast) {
  0         0  
  0         0  
594 0 0       0 return 0 if $main_called;
595 0 0       0 return 1 if is_main($ast);
596 0 0       0 return 0 unless ref($ast) eq LIST;
597 0         0 for my $node (@$ast) {
598 0 0       0 return 1 if is_main($node);
599             }
600 0         0 return 0;
601             }
602              
603             #------------------------------------------------------------------------------
604             # YS expression reader.
605             #
606             # Converts these special forms:
607             # x(...) -> (x ...)
608             # (x + y) -> (+ x y)
609             # (x + y * z) -> (+ x (* y z))
610             # x(y + z) -> (x (+ y z))
611             #------------------------------------------------------------------------------
612              
613             my $dyn = qr<(?:\*$sym\*)>;
614             my $op = qr{(?:[-+*/]|[<>=]=?|and|or|\.\.)};
615              
616             my $pn = qr=(?:->|~@|[\'\`\[\]\{\}\(\)\~\^\@])=;
617             # my $pn = qr<(?:~@|[\'\`\[\]\{\}\(\)\~\^\@])>;
618              
619             my $re = qr<(?:/(?:\\.|[^\\\/])*/)>;
620             my $str = qr<(?:#?"(?:\\.|[^\\"])*"?)>;
621             my $tok = qr<[^\s\[\]{}('",;)]>;
622             my $ws = qr<(?:[\s,])>;
623              
624             sub tokenize {
625             [
626             map {
627 80 100   80 0 2400 s/::/./g if /^\w+(?:::\w+)+$/;
  220         440  
628 220         501 $_;
629             }
630             $_[0] =~ /
631             $ws*
632             (
633             $re |
634             $pn |
635             $str |
636             $dyn |
637             $op(?=\s) |
638             $sym\( |
639             '?$sym |
640             '?$tok
641             )
642             /xog
643             ];
644             }
645              
646 80     80 0 108 sub read_ysexpr($expr) {
  80         124  
  80         130  
647 80         166 $expr = lingy_expr($expr);
648 80         301 my @ast = Lingy::Reader->new->read_str($expr);
649 80 100       10774 return @ast if wantarray;
650 6 50       24 ZZZ [@ast, "Should have got exactly one result"]
651             unless @ast == 1;
652 6         17 return $ast[0];
653 0         0 Lingy::Reader->new->read_str($expr)
654             }
655              
656 80     80 0 114 sub lingy_expr($expr) {
  80         151  
  80         102  
657 80         175 my $tokens = tokenize($expr);
658 80         280 my $self = bless { tokens => $tokens }, __PACKAGE__;
659 80         135 my @groups;
660 80         198 while (@$tokens) {
661 70         147 push @groups, eval { $self->group };
  70         158  
662 70 50       234 die "Failed to parse expr '$expr': '$@'" if $@;
663             }
664             join ' ', map {
665 80 100       226 ref($_) ? $self->group_print($_) : $_;
  70         278  
666             } @groups;
667             }
668              
669 72     72 0 99 sub group($s) {
  72         106  
  72         109  
670 72         115 my $tokens = $s->{tokens};
671 72         137 my $token = shift @$tokens;
672 72 100 100     372 if (@$tokens >= 2 and
      66        
673             $tokens->[0] eq '->' and
674             $tokens->[1] =~ /^$sym\($/
675             ) {
676 1         4 shift(@$tokens);
677 1         4 my $method = shift(@$tokens);
678 1 50       7 $method =~ s/\($// or die;
679 1         5 return [ '.', $token, $s->group_call($method) ];
680             }
681 71 100       1310 $token =~ s/^($sym)\($/$1/ ? $s->group_call($token) :
    100          
    100          
    50          
    100          
682             $token =~ /^\('\s$/ ? $s->group_list(1) :
683             $token eq '(' ? $s->group_list(0) :
684             $token eq '`' ? $token :
685             $token =~ /^$re$/ ? '#"' . substr($token, 1, length($token) - 2) . '"' :
686             $token;
687             # die "Unknown token '$token'";
688             }
689              
690 28     28 0 49 sub group_list($s, $l) {
  28         58  
  28         46  
  28         36  
691 28         50 my $tokens = $s->{tokens};
692 28         74 my $group = $s->group_rest;
693 28 100 66     752 return $group if $l or @$group != 3 or $group->[1] !~ qr<^$op$>;
      100        
694              
695 18         64 my $oper = $group->[1];
696 18 100       56 $oper = '-range' if $oper eq '..';
697              
698             # TODO Support infix group > 3
699 18         83 [ $oper, $group->[0], $group->[2] ];
700             }
701              
702 12     12 0 29 sub group_call($s, @t) {
  12         20  
  12         25  
  12         18  
703 12         24 my $tokens = $s->{tokens};
704 12         31 my $group = [@t];
705 12         34 my $rest = $s->group_rest;
706 12 50 33     46 if (@$rest == 3 and $rest->[1] =~ qr<^$op$>) {
707 0         0 $rest = [ $rest->[1], $rest->[0], $rest->[2] ];
708 0         0 $rest = ([$rest]);
709             }
710 12         38 push @$group, @$rest;
711 12         44 return $group;
712             }
713              
714 40     40 0 54 sub group_rest($s) {
  40         63  
  40         52  
715 40         62 my $tokens = $s->{tokens};
716 40         74 my $rest = [];
717 40         92 while (@$tokens) {
718 148 100       1464 if ($tokens->[0] eq ')') {
    100          
719 40         68 shift @$tokens;
720 40         116 return $rest;
721             } elsif ($tokens->[0] =~ qr<^$sym?\('?$>) {
722 2         14 push @$rest, $s->group;
723             } else {
724 106         363 push @$rest, shift @$tokens;
725             }
726             }
727 0         0 die "Failed to parse expression";
728             }
729              
730 41     41 0 68 sub group_print($s, $g) {
  41         62  
  41         54  
  41         55  
731             '(' .
732             join(' ',
733             map {
734 41 100       70 ref($_) ? $s->group_print($_) : $_;
  123         471  
735             } @$g
736             )
737             . ')';
738             }
739              
740              
741             #------------------------------------------------------------------------------
742             # AST Composer Methods
743             #------------------------------------------------------------------------------
744             sub compose_dom {
745 58     58 0 122 my ($self) = @_;
746 58         141 my $node = $self->compose_node;
747 58         211 $node->{xtop} = 1;
748 58         160 tag_node($node);
749 57         114 return $node;
750             }
751              
752             sub compose_node {
753 228     228 0 355 my ($self) = (@_, '');
754 228         379 my $events = $self->{events};
755 228         539 while (@$events) {
756 344         529 my $event = shift(@$events);
757 344 100       1462 if ($event->{type} =~ /^[+=](map|seq|val|ali)$/) {
758 228         589 my $composer = "compose_$1";
759 228         619 my $node = $self->$composer($event);
760 228 50       552 if ((my $ytag = $event->{ytag}) ne '-') {
761 0 0       0 $ytag =~ s/^!(\w*)$/$1/ or XXX $event;
762 0   0     0 $node->{ytag} = $ytag || ref($node);
763             }
764 228         475 return $node;
765             }
766             }
767             }
768              
769             sub compose_map {
770 57     57 0 146 my ($self, $event) = @_;
771 57         177 my $map = 'map'->new($event);;
772 57         103 my $events = $self->{events};
773 57         150 while (@$events) {
774 120 100       411 shift(@$events), return $map if $events->[0]{type} eq '-map';
775 63         155 my $k = $self->compose_node;
776 63         265 $k->{xkey} = 1;
777 63         145 my $v = $self->compose_node;
778 63         173 my $pair = 'pair'->new($k, $v);
779 63         144 $map->add($pair);
780             }
781 0         0 XXX $map, "problem composing map";
782             }
783              
784             sub compose_seq {
785 22     22 0 53 my ($self, $event) = @_;
786 22         109 my $seq = 'seq'->new($event);
787 22         41 my $events = $self->{events};
788 22         70 while (@$events) {
789 66 100       201 shift(@$events), return $seq if $events->[0]{type} eq '-seq';
790 44         108 my $elem = $self->compose_node;
791 44         99 $seq->add($elem);
792             }
793 0         0 XXX $seq, "problem composing seq";
794             }
795              
796             sub compose_val {
797 149     149 0 265 my ($self, $event) = @_;
798 149         338 'val'->new($event);
799             }
800              
801             sub compose_ali {
802 0     0 0 0 my ($self, $event) = @_;
803 0         0 'ali'->new($event);
804             }
805              
806             #------------------------------------------------------------------------------
807             # AST Tag Resolution Methods
808             #------------------------------------------------------------------------------
809             {
810 11     11   119 no warnings 'redefine';
  11         161  
  11         40443  
811             sub YAMLScript::Common::_dump {
812 0     0   0 (my $type = (caller(1))[3]) =~ s/.*://;
813 0         0 my $sub = (caller(2))[3];
814 0         0 my $line = (caller(1))[2];
815 0         0 require YAML::PP;
816 0         0 my $dump = YAML::PP->new(
817             schema => ['Core', 'Perl', '-dumpcode'],
818             )->dump_string(@_) . "\e[0;33m... $type $sub $line\e[0m\n\n";
819 0         0 $dump =~ s/\A(.*)/\n\e[0;33m$1\e[0m/;
820 0         0 $dump;
821             }
822             }
823              
824 0     0 0 0 sub tag_error($msg) { ZZZ "$msg: '$_'" }
  0         0  
  0         0  
  0         0  
825              
826 165     165 0 268 sub tag_node($n) {
  165         231  
  165         241  
827 165 50       399 if ($n->{ytag}) {
828 0 0       0 if ($n->{ytag} ne 'yamlscript') {
829 0         0 return 1;
830             }
831             }
832 165         306 $n = transform($n);
833 165 100       330 if (is_map($n)) {
    100          
834 55         125 for my $p (pairs($n)) {
835 57 50 66     122 tag_catch($p) or
      100        
      100        
      100        
      100        
      66        
      66        
      66        
      100        
      66        
836             tag_defn_multi($p) or
837             tag_defn($p) or
838             tag_def($p) or
839             tag_if($p) or
840             tag_fn($p) or
841             tag_let($p) or
842             tag_loop($p) or
843             tag_try($p) or
844             tag_when($p) or
845             tag_call($p) or
846             XXX $p, "Unable to implicitly tag this map pair.";
847             }
848 54   50     205 $n->{ytag} //= 'module';
849             }
850             elsif (is_seq($n)) {
851 23         38 for my $e (@{$n->{elem}}) {
  23         61  
852 48         114 tag_node($e);
853             }
854 23         56 $n->{ytag} = 'do';
855             }
856             else {
857 87         169 tag_val($n);
858             }
859              
860 164         627 1;
861             }
862              
863 165     165 0 232 sub transform($n) {
  165         263  
  165         223  
864 165 100       312 if (is_map($n)) {
865 55         133 for my $p (pairs($n)) {
866 57         165 my ($k, $v) = @$p;
867              
868             $k->{text} =
869             "$k" eq '???' ? 'cond' :
870             "$k" eq '^^^' ? 'recur' :
871 57 50       153 $k->{text};
    100          
872              
873 57 100 100     152 if ("$k" eq 'cond' and is_map($v)) {
874             $p->[1] = bless {
875             elem => [
876 4         7 map { delete($_->{xkey}); $_ }
  4         11  
877 1         3 map { @$_ } @{$v->{pair}}
  2         5  
  1         3  
878             ],
879             }, 'seq';
880             }
881             }
882             }
883              
884 165         320 return $n;
885             }
886              
887 87     87 0 112 sub tag_val($n) {
  87         117  
  87         124  
888 87 50 66     153 if (e_tag($n) ne '-') {
    100          
    50          
889 0         0 $n->{ytag} = substr(e_tag($n), 1);
890             } elsif (is_double($n) or is_literal($n)) {
891 2 50 33     16 ($n->{xtop} and tag_ysexpr($n)) or
      33        
892             tag_istr($n) or
893             tag_str($n);
894             } elsif (is_plain($n)) {
895 85 100 66     185 is_key($n) or
896             tag_scalar($n) or
897             tag_ysexpr($n);
898             } else {
899 0         0 tag_str($n);
900             }
901             }
902              
903 36     36 0 61 sub tag_call($p) {
  36         50  
  36         56  
904 36         63 my ($k, $v) = @$p;
905 36 50       279 if ($k =~ /^$sym($bp?)$/) {
906 36         103 my $args = $1;
907             $k->{ytag} =
908 36 100       66 "$k" eq 'use'
909             ? "$k" :'call';
910              
911             # Empty (null) value
912 36 100 100     94 if (is_plain($v) and text($v) eq '') {
913 3 100       13 err "Use 'foo():' for a call with no args"
914             if $args eq '';
915             }
916              
917 35         113 tag_node($v);
918             }
919             }
920              
921 57     57 0 152 sub tag_catch($n) {
  57         85  
  57         77  
922 57 50       1468 $n->{ytag} = 'catch' if $n =~ /^catch\($sym\)$/;
923             }
924              
925 50     50 0 101 sub tag_def($p) {
  50         75  
  50         68  
926 50         113 my ($k, $v) = @$p;
927 50 100       1048 return unless $k =~ /^$sym\s*=$/;
928 6         31 $k->{ytag} = 'def';
929 6         38 tag_node($v);
930             }
931              
932 56     56 0 98 sub tag_defn($p) {
  56         77  
  56         87  
933 56         108 my ($k, $v) = @$p;
934 56 100       678 return unless $k =~ /^(?:defn|defmacro)\s+$sym$bp$/;
935 6         29 $k->{ytag} = 'defn';
936 6         17 tag_node($v);
937             }
938              
939 57     57 0 107 sub tag_defn_multi($p) {
  57         81  
  57         81  
940 57         117 my ($k, $v) = @$p;
941 57 100 66     780 return unless $k =~ /^(?:defn|defmacro)\s+$sym$/ and is_map($v);
942 1         3 for my $p (pairs($v)) {
943 4 50       50 return unless $p->[0] =~ /^$bp$/;
944             }
945 1         4 $k->{ytag} = 'defn_multi';
946 1         3 for my $p (pairs($v)) {
947 4         11 my ($k, $v) = @$p;
948 4         7 tag_node($v);
949             }
950 1         6 return 1;
951             }
952              
953 44     44 0 104 sub tag_if($p) {
  44         65  
  44         59  
954 44         92 my ($k, $v) = @$p;
955 44 100       78 return unless $k =~ /^if +\S/;
956 2         6 $k->{ytag} = 'if';
957 2         9 tag_node($v);
958             }
959              
960 2     2 0 5 sub tag_istr($n) {
  2         4  
  2         3  
961 2 50       148 $n->{ytag} = 'istr' if $n =~ /(\$$sym|\$\()/;
962             }
963              
964 42     42 0 92 sub tag_fn($p) {
  42         67  
  42         59  
965 42         78 my ($k, $v) = @$p;
966 42 100       342 return unless $k =~ /^fn\s+$bp$/;
967 2         10 $k->{ytag} = 'fn';
968 2         9 tag_node($v);
969             }
970              
971 40     40 0 71 sub tag_let($n) {
  40         63  
  40         55  
972 40 50       235 $n->{ytag} = 'let1' if $n =~ /^let$/;
973             }
974              
975 40     40 0 94 sub tag_loop($p) {
  40         57  
  40         54  
976 40         75 my ($k, $v) = @$p;
977 40 100       68 return unless $k =~ /^loop +\S/;
978 1         3 $k->{ytag} = 'loop';
979 1         5 tag_node($v);
980             }
981              
982 85     85 0 117 sub tag_scalar($n) {
  85         110  
  85         110  
983 85         125 local $_ = $n;
984             $n->{ytag} =
985             /^(true|false)$/ ? 'boolean' :
986             /^-?\d+$/ ? 'int' :
987             /^-?\d+\.\d*$/ ? 'float' :
988             /^:$sym$/ ? 'keyword' :
989             /^null$/ ? 'null' :
990 85 100       185 /^$sym$/ ? do {
    50          
    50          
    50          
    100          
    50          
991 27         65 $n->{text} =~ s/::/./g;
992 27         146 'sym';
993             } :
994             return;
995             }
996              
997 2     2 0 5 sub tag_str($n) {
  2         4  
  2         4  
998 2         6 $n->{ytag} = 'str';
999             }
1000              
1001 39     39 0 70 sub tag_try($n) {
  39         58  
  39         56  
1002 39 50       191 $n->{ytag} = 'try' if $n =~ /^try$/;
1003             }
1004              
1005 39     39 0 61 sub tag_when($p) {
  39         59  
  39         46  
1006 39         73 my ($k, $v) = @$p;
1007 39 100       58 return unless $k =~ /(?:\)|. )[?|]$/;
1008 3         7 $k->{ytag} = 'when';
1009 3         8 tag_node($v);
1010             }
1011              
1012 39     39 0 63 sub tag_ysexpr($n) {
  39         60  
  39         51  
1013 39         102 $n->{text} =~ s/^\.(?!\d)//;
1014 39         95 $n->{ytag} = 'ysexpr';
1015             }
1016              
1017             #------------------------------------------------------------------------------
1018             # Event and Node Classes
1019             #------------------------------------------------------------------------------
1020             {
1021             package event;
1022             sub new {
1023 572     572   1016 my ($class, $line) = @_;
1024 572         835 chomp $line;
1025 572         905 my $self = bless {}, $class;
1026 572         1769 @{$self}{@event_keys} = split /\t/, $line;
  572         2679  
1027 572         1749 return $self;
1028             }
1029             }
1030              
1031             {
1032             package pair;
1033             sub new {
1034 63     63   131 my ($class, $k, $v) = @_;
1035 63         148 bless [$k, $v], $class;
1036             }
1037 57     57   85 sub key($p) { $p->[0] }
  57         90  
  57         75  
  57         121  
1038 0     0   0 sub val($p) { $p->[1] }
  0         0  
  0         0  
  0         0  
1039             }
1040              
1041             {
1042             package map;
1043             sub new {
1044 57     57   130 my ($class, $event, @pairs) = @_;
1045 57         165 my $self = bless {
1046             pair => [@pairs],
1047             }, $class;
1048             $refs{$event->{anch}} = $self
1049 57 50       189 if $event->{anch} ne '-';
1050 57         248 $events{Scalar::Util::refaddr($self)} = $event;
1051 57         139 return $self;
1052             }
1053             sub add {
1054 63     63   127 my ($self, $pair) = @_;
1055 63         98 push @{$self->{pair}}, $pair;
  63         223  
1056             }
1057 171     171   622 sub pair { $_[0]->{pair} }
1058             }
1059              
1060             {
1061             package seq;
1062             sub new {
1063 57     57   137 my ($class, $event, @elems) = @_;
1064 57         178 my $self = bless {
1065             elem => [@elems],
1066             }, $class;
1067 57 50       147 if ($event) {
1068             $refs{$event->{anch}} = $self
1069 57 50       139 if $event->{anch} ne '-';
1070 57         202 $events{Scalar::Util::refaddr($self)} = $event;
1071             }
1072 57         123 return $self;
1073             }
1074             sub add {
1075 44     44   80 my ($self, $value) = @_;
1076 44         65 push @{$self->{elem}}, $value;
  44         102  
1077 44         97 return $self;
1078             }
1079 82     82   282 sub elem { $_[0]->{elem} }
1080             }
1081              
1082             {
1083             package val;
1084 11     11   103 use overload '""' => sub { $_[0]->{text} };
  11     1178   28  
  11         145  
  1178         24767  
1085             my %escapes = (
1086             'n' => "\n",
1087             't' => "\t",
1088             '\\' => '\\',
1089             '"' => '"',
1090             );
1091             sub new {
1092 183     183   348 my ($class, $event, $text) = @_;
1093 183   100     797 $text //= $event->{valu} // '';
      100        
1094 183         328 $text =~ s/\\([nt\\\"])/$escapes{$1}/g;
1095 183         448 my $self = bless {
1096             text => $text,
1097             }, $class;
1098 183 100       403 if ($event) {
1099 149         292 delete $event->{valu};
1100             $refs{$event->{anch}} = $self
1101 149 50       332 if $event->{anch} ne '-';
1102 149         487 $events{Scalar::Util::refaddr($self)} = $event;
1103             }
1104 183         396 return $self;
1105             }
1106             }
1107              
1108             {
1109             package ali;
1110             sub new {
1111 0     0     my ($class, $event) = @_;
1112             my $self = bless {
1113             name => $event->{valu},
1114 0           }, $class;
1115 0           delete $event->{valu};
1116 0           $events{Scalar::Util::refaddr($self)} = $event;
1117 0           return $self;
1118             }
1119             }
1120              
1121             1;