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   87 use strict; use warnings;
  11     11   20  
  11         309  
  11         54  
  11         20  
  11         468  
2              
3             # Needed for Perl versions < 5.20
4             BEGIN {
5 11 50   11   755 warnings->unimport('experimental::signatures')
  11     11   77  
  11         21  
  11         492  
6             if eval "use warnings 'experimental::signatures'; 1";
7             }
8 11     11   75 use feature 'signatures';
  11         3486  
  11         13435  
9              
10             package YAMLScript::Reader;
11              
12 11     11   84 use YAMLScript::Common;
  11         20  
  11         2288  
13 11     11   5272 use Lingy::Reader;
  11         41867  
  11         387  
14              
15 11     11   75 use base 'Lingy::Reader';
  11         20  
  11         1373  
16              
17 11     11   6189 use Regexp::Common;
  11         56081  
  11         39  
18 11     11   1788463 use Scalar::Util 'refaddr';
  11         31  
  11         128246  
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 497812 sub new { bless {}, shift }
32              
33             sub read_str {
34 79     79 0 15483 my $self = shift;
35 79         223 my ($str) = @_;
36 79 100       257 if ($read_ys) {
37 7         17 return $self->read_ys($str);
38             } else {
39 72         382 return $self->SUPER::read_str(@_);
40             }
41             }
42              
43             sub read_ys {
44 58     58 0 166 my ($self, $yaml, $file) = (@_, '');
45              
46 58         221 $self->{yaml} = $yaml;
47 58         116 $self->{file} = $file;
48              
49 58         416 %events = ();
50 58         98 %functions = ();
51 58         104 %refs = ();
52              
53 58         148 $self->{events} = $self->parse_yaml_pp($yaml);
54 58         188 my $dom = $self->compose_dom;
55 57 50       226 my $ast = $file
56             ? $self->construct_ast($dom)
57             : $self->construct_expr($dom);
58              
59 57         408 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 122 my ($self, $yaml) = @_;
102 58         325 require YAML::PP::Parser;
103 58         140 my $events = [];
104             YAML::PP::Parser->new(
105             receiver => sub {
106 539     539   103710 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     3408 ($event->{tag} || '-'),
      50        
      50        
112             );
113 539 100       1230 if ($event->{name} eq 'scalar_event') {
114 149         284 my $value = $event->{value};
115 149         244 my $style = $event->{style};
116 149         332 $value =~ s/\\/\\\\/g;
117 149         222 $value =~ s/\n/\\n/g;
118 149 50       458 push @event,
    100          
119             (
120             $style == 1 ? ':' :
121             $style == 4 ? '|' :
122             '"'
123             ),
124             $value;
125             }
126 539         2878 push @$events, join "\t", @event;
127             },
128 58         467 )->parse_string($yaml);
129 58         1211 [ 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 111 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 716 sub L { LIST->new([@_]) }
151 20     20 0 93 sub N { NUMBER->new($_[0]) }
152 138     138 0 462 sub S { SYMBOL->new($_[0]) }
153 2     2 0 30 sub T { STRING->new($_[0]) }
154 14     14 0 88 sub V { VECTOR->new([@_]) }
155              
156 17     17 0 35 sub DEF { S 'def' }
157 6     6 0 18 sub DO { S 'do' }
158 14     14 0 81 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 464 sub event($n) { $events{refaddr($n)} }
  368         441  
  368         429  
  368         1942  
174 281     281 0 375 sub e_style($n) { event($n)->{styl} }
  281         354  
  281         345  
  281         436  
175 87     87 0 134 sub e_tag($n) { event($n)->{ytag} }
  87         130  
  87         117  
  87         201  
176 531     531 0 650 sub is_map($n) { ref($n) eq 'map' }
  531         677  
  531         647  
  531         1557  
177 234     234 0 316 sub is_seq($n) { ref($n) eq 'seq' }
  234         319  
  234         294  
  234         678  
178 328     328 0 485 sub is_val($n) { ref($n) eq 'val' }
  328         412  
  328         385  
  328         1035  
179 292     292 0 364 sub is_pair($n) { ref($n) eq 'pair' }
  292         373  
  292         347  
  292         858  
180 85     85 0 116 sub is_key($n) { $n->{xkey} }
  85         211  
  85         115  
  85         256  
181 121 100   121 0 187 sub is_plain($n) { is_val($n) and e_style($n) eq ':' }
  121         158  
  121         159  
  121         213  
182 87 50   87 0 131 sub is_double($n) { is_val($n) and e_style($n) eq '"' }
  87         119  
  87         115  
  87         169  
183 85 50   85 0 139 sub is_literal($n) { is_val($n) and e_style($n) eq '|' }
  85         117  
  85         126  
  85         137  
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 26 sub is_def($n) { is_map($n) and tag(key(first_pair($n))) eq 'def' }
  14         18  
  14         19  
  14         31  
193              
194 171 50   171 0 224 sub assert_map($n) { is_map($n) or ZZZ($n) }
  171         246  
  171         214  
  171         282  
195 72 50   72 0 95 sub assert_seq($n) { is_seq($n) or ZZZ($n) }
  72         94  
  72         93  
  72         156  
196 35 50   35 0 56 sub assert_val($n) { is_val($n) or ZZZ($n) }
  35         49  
  35         55  
  35         65  
197 57 50   57 0 76 sub assert_pair($n) { is_pair($n) or ZZZ($n) }
  57         81  
  57         76  
  57         95  
198 12 50   12 0 21 sub assert_elems($n) { assert_seq($n); @{$n->elem} > 0 or ZZZ($n) }
  12         15  
  12         16  
  12         51  
  12         21  
  12         35  
199 2 50   2 0 3 sub assert_pairs($n) { assert_map($n); @{$n->pair} > 0 or ZZZ($n) }
  2         4  
  2         2  
  2         6  
  2         3  
  2         20  
200 169     169 0 232 sub pairs($n) { assert_map($n); @{$n->pair} }
  169         223  
  169         364  
  169         385  
  169         342  
  169         396  
201 60     60 0 275 sub elems($n) { assert_seq($n); @{$n->elem} }
  60         91  
  60         76  
  60         149  
  60         104  
  60         147  
202 237     237 0 316 sub tag($n) { $n->{ytag} }
  237         382  
  237         291  
  237         447  
203 57     57 0 83 sub key($p) { assert_pair($p); $p->key }
  57         79  
  57         81  
  57         145  
  57         134  
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 54 sub text($v) { assert_val($v); $v->{text} }
  35         48  
  35         47  
  35         91  
  35         658  
207 12     12 0 16 sub first_elem($n) { assert_elems($n); (elems($n))[0] }
  12         18  
  12         15  
  12         30  
  12         43  
208 2     2 0 4 sub first_pair($n) { assert_pairs($n); (pairs($n))[0] }
  2         3  
  2         14  
  2         9  
  2         6  
209              
210 57     57 0 100 sub construct_expr($s, $n) {
  57         80  
  57         74  
  57         79  
211 57         161 my @ast = $s->construct($n);
212              
213 57 100       202 @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 528 sub construct($s, $n) {
  235         308  
  235         315  
  235         285  
238 235 100       443 my $tag = is_pair($n) ? tag(key($n)) : tag($n);
239 235 50       484 XXX $n, "No tag for node" unless $tag;
240 235         441 my $constructor = "construct_$tag";
241 235         793 $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 57 sub construct_call($s, $p) {
  34         45  
  34         44  
  34         44  
251 34         76 my ($k, $v) = @$p;
252 34 50       73 "$k" =~ /^($sym)($bp?)$/ or die;
253 34         3868 my $fn = $1;
254 34         78 my $args = $2; # TODO add these args to value args
255 34 100       79 if ($args) {
256 11 50       59 $args =~ s/^\((.*)\)$/$1/ or die;
257             }
258 34         87 $fn =~ s/^(let|try|catch)$/$1*/;
259 34 50       87 $main_called = 1 if $fn eq 'main';
260 34         117 $args = 'val'->new(undef, $args);
261 34         85 $args->{ytag} = 'ysexpr';
262 34 100       82 $v = SEQ($v) unless is_seq($v);
263 34         93 L(S($fn), map $s->construct($_), $args, elems($v));
264             }
265              
266 5     5 0 10 sub construct_def($s, $p) {
  5         9  
  5         9  
  5         8  
267 5         14 my ($k, $v) = @$p;
268 5 50       11 "$k" =~ /^($sym)\s*=$/ or die;
269 5         26 my $sym = S($1);
270 5         55 my $rhs = $s->construct($v);
271 5         43 return L(DEF, $sym, $rhs);
272             }
273              
274             sub get_sig {
275 12     12 0 28 my ($sig) = @_;
276 12         24 my $args = [];
277 12         20 my $dargs = [];
278 12         426 while ($sig =~ s/^($sym)(?=,?\s|$),?\s*//) {
279 16         152 push @$args, symbol($1);
280             }
281 12 100       306 if ($sig =~ s/^\*($sym)//) {
282 3         10 push @$args, symbol('&'), symbol($1);
283             }
284             else {
285 9 100       189 if ($sig =~ /^($sym)=/) {
286 1         6 push @$args, symbol('&'), symbol('_args_');
287             }
288 9         296 while ($sig =~ s/^($sym)=(\S+),?\s*//) {
289 1         7 my ($s, $x) = ($1, $2);
290 1         4 push @$dargs, $1;
291 1         3 push @$dargs, read_ysexpr($x);
292             }
293             }
294 12 50       72 err "Can't parse function signature '$_[0]'"
295             if length($sig);
296 12         37 return ($args, $dargs);
297             }
298              
299 6     6 0 11 sub construct_defn($s, $p) {
  6         10  
  6         10  
  6         8  
300 6         33 my ($k, $v) = @$p;
301 6         20 my ($def, $name, $args, $body) = $s->_defn_parse($k, $v, 0);
302 6         22 return L($def, $name, V(@$args), @$body);
303             }
304              
305 1     1 0 2 sub construct_defn_multi($s, $p) {
  1         2  
  1         2  
  1         3  
306 1         4 my ($k, $v) = @$p;
307 1 50       3 text($k) =~ /^(defn|defmacro)\s+($sym)$/ or die;
308 1         6 my $def = $1;
309 1         6 my $name = S($2);
310             my @defs = map {
311 1         12 my ($k, $v) = @$_;
  4         21  
312 4         23 my (undef, undef, $args, $body) = $s->_defn_parse($k, $v, 1);
313 4         12 L(V(@$args), @$body);
314             } pairs($v);
315 1         10 return L($def, $name, @defs);
316             }
317              
318 2     2 0 4 sub construct_fn($s, $p) {
  2         3  
  2         3  
  2         4  
319 2         5 my ($k, $v) = @$p;
320 2         7 my ($def, $name, $args, $body) = $s->_defn_parse($k, $v, 0);
321 2         8 return L(FN, V(@$args), @$body);
322             }
323              
324 12     12   21 sub _defn_parse($s, $k, $v, $m) {
  12         19  
  12         17  
  12         19  
  12         22  
  12         15  
325 12         25 my ($def, $name, $sig);
326 12 100       30 if ($m) {
327 4 50       9 text($k) =~ /^($sym?)?\((.*)\)$/ or XXX $k;
328 4         12 $def = '';
329 4         11 $name = S($1);
330 4         32 $sig = $2;
331             } else {
332 8 50       20 text($k) =~ /^(fn|defn|defmacro)\s+($sym?)?\((.*)\)$/ or XXX $k;
333 8         31 $def = S($1);
334 8         72 $name = S($2);
335 8         53 $sig = $3;
336             }
337 12         68 my ($args, $dargs) = get_sig($sig);
338 12         35 my $defn = L( DEF, $name, L( FN, L, nil ) );
339 12 100       65 my $seq = is_seq($v) ? $v : SEQ($v);
340 12         34 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     69 : map $s->construct($_), @{$seq->elem},
  10         28  
345             ];
346 12         100 return $def, $name, $args, $body;
347             }
348              
349 6     6 0 24 sub construct_do($s, $n) {
  6         10  
  6         10  
  6         11  
350 6         17 my @elems = elems($n);
351 6 100       61 if (@elems == 1) {
352 3         16 $s->construct($elems[0]);
353             } else {
354 3         21 L(
355             DO,
356             map $s->construct($_), @elems,
357             );
358             }
359             }
360              
361 2     2 0 10 sub construct_if($s, $p) {
  2         12  
  2         4  
  2         5  
362 2         6 my ($k, $v) = @$p;
363 2 50       7 "$k" =~ /^if +($bp)/ or die;
364 2         277 my $cond = read_ysexpr($1);
365 2 50       9 my @elems = is_seq($v) ? elems($v) : $v;
366 2         10 L(
367             S('if'),
368             $cond,
369             map $s->construct($_), @elems,
370             );
371             }
372              
373 19     19 0 25 sub construct_int($s, $n) { N("$n") }
  19         33  
  19         28  
  19         29  
  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 13 sub construct_let($s, $n, $a, $d) {
  2         4  
  2         4  
  2         3  
  2         4  
  2         2  
398 2         6 my @elems = elems($n);
399 2 50 66     16 if (is_map($elems[0]) and @{$elems[0]->{pair}} > 1) {
  1         11  
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         3 my $i = 0;
410 2         7 while (@$d) {
411 1         4 my ($sym, $form) = splice(@$d, 0, 2);
412 1         3 push @defs, S($sym), L(S('nth'), S('_args_'), N($i), $form);
413 1         6 $i++;
414             }
415 2   66     10 while (@elems and is_def($elems[0])) {
416 1         13 my $d = shift @elems;
417 1         7 my ($p) = pairs($d);
418 1         10 my ($k, $v) = @$p;
419 1 50       4 (my $sym = "$k") =~ s/\s+=$// or die;
420 1         15 push @defs, S($sym), $s->construct($v);
421             }
422             L(
423 2         6 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         1  
455 1         3 my ($k, $v) = @$p;
456 1 50       4 "$k" =~ /^loop +($bs)/ or die;
457 1         215 my $bindings = read_ysexpr($1);
458 1 50       5 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 89 sub construct_module($s, $n) {
  53         78  
  53         68  
  53         68  
467 53         109 my @forms = map $s->construct($_), pairs($n);
468 53 100       628 return $forms[0] if @forms == 1;
469 2         10 L(DO, @forms);
470             }
471              
472 2     2 0 19 sub construct_str($s, $n) {
  2         5  
  2         4  
  2         4  
473 2         9 T("$n");
474             }
475              
476 27     27 0 52 sub construct_sym($s, $n) {
  27         41  
  27         87  
  27         37  
477 27         57 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 3 sub construct_use($s, $p) {
  1         4  
  1         2  
  1         2  
500 1         3 my ($k, $v) = @$p;
501 1         4 $v = $s->construct($v);
502 1 50       11 if (ref($v) eq SYMBOL) {
503 1         2 $v = L(S('quote'), $v);
504             }
505 1         43 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 6 sub construct_when($s, $p) {
  3         10  
  3         5  
  3         5  
513 3         7 my ($k, $v) = @$p;
514 3 50       7 (my $expr = "$k") =~ s/ ?([?|])$// or die;
515 3 100       19 my $fn = $1 eq '?' ? 'when' : 'when-not';
516 3         8 my $cond = read_ysexpr($expr);
517 3 50       9 my @elems = is_seq($v) ? elems($v) : $v;
518 3         10 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 117 sub construct_ysexpr($s, $n) {
  73         102  
  73         96  
  73         96  
532 73         145 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 2490 s/::/./g if /^\w+(?:::\w+)+$/;
  220         451  
628 220         510 $_;
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 131 sub read_ysexpr($expr) {
  80         120  
  80         111  
647 80         146 $expr = lingy_expr($expr);
648 80         327 my @ast = Lingy::Reader->new->read_str($expr);
649 80 100       11240 return @ast if wantarray;
650 6 50       26 ZZZ [@ast, "Should have got exactly one result"]
651             unless @ast == 1;
652 6         18 return $ast[0];
653 0         0 Lingy::Reader->new->read_str($expr)
654             }
655              
656 80     80 0 112 sub lingy_expr($expr) {
  80         114  
  80         101  
657 80         142 my $tokens = tokenize($expr);
658 80         300 my $self = bless { tokens => $tokens }, __PACKAGE__;
659 80         131 my @groups;
660 80         196 while (@$tokens) {
661 70         137 push @groups, eval { $self->group };
  70         163  
662 70 50       239 die "Failed to parse expr '$expr': '$@'" if $@;
663             }
664             join ' ', map {
665 80 100       221 ref($_) ? $self->group_print($_) : $_;
  70         282  
666             } @groups;
667             }
668              
669 72     72 0 103 sub group($s) {
  72         109  
  72         88  
670 72         120 my $tokens = $s->{tokens};
671 72         138 my $token = shift @$tokens;
672 72 100 100     371 if (@$tokens >= 2 and
      66        
673             $tokens->[0] eq '->' and
674             $tokens->[1] =~ /^$sym\($/
675             ) {
676 1         5 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       1373 $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 53 sub group_list($s, $l) {
  28         46  
  28         43  
  28         38  
691 28         55 my $tokens = $s->{tokens};
692 28         79 my $group = $s->group_rest;
693 28 100 66     717 return $group if $l or @$group != 3 or $group->[1] !~ qr<^$op$>;
      100        
694              
695 18         72 my $oper = $group->[1];
696 18 100       59 $oper = '-range' if $oper eq '..';
697              
698             # TODO Support infix group > 3
699 18         81 [ $oper, $group->[0], $group->[2] ];
700             }
701              
702 12     12 0 39 sub group_call($s, @t) {
  12         25  
  12         27  
  12         21  
703 12         27 my $tokens = $s->{tokens};
704 12         28 my $group = [@t];
705 12         32 my $rest = $s->group_rest;
706 12 50 33     47 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         35 push @$group, @$rest;
711 12         47 return $group;
712             }
713              
714 40     40 0 63 sub group_rest($s) {
  40         54  
  40         54  
715 40         71 my $tokens = $s->{tokens};
716 40         62 my $rest = [];
717 40         105 while (@$tokens) {
718 148 100       1458 if ($tokens->[0] eq ')') {
    100          
719 40         84 shift @$tokens;
720 40         152 return $rest;
721             } elsif ($tokens->[0] =~ qr<^$sym?\('?$>) {
722 2         15 push @$rest, $s->group;
723             } else {
724 106         374 push @$rest, shift @$tokens;
725             }
726             }
727 0         0 die "Failed to parse expression";
728             }
729              
730 41     41 0 74 sub group_print($s, $g) {
  41         58  
  41         59  
  41         55  
731             '(' .
732             join(' ',
733             map {
734 41 100       87 ref($_) ? $s->group_print($_) : $_;
  123         458  
735             } @$g
736             )
737             . ')';
738             }
739              
740              
741             #------------------------------------------------------------------------------
742             # AST Composer Methods
743             #------------------------------------------------------------------------------
744             sub compose_dom {
745 58     58 0 116 my ($self) = @_;
746 58         138 my $node = $self->compose_node;
747 58         213 $node->{xtop} = 1;
748 58         156 tag_node($node);
749 57         110 return $node;
750             }
751              
752             sub compose_node {
753 228     228 0 384 my ($self) = (@_, '');
754 228         369 my $events = $self->{events};
755 228         617 while (@$events) {
756 344         532 my $event = shift(@$events);
757 344 100       2059 if ($event->{type} =~ /^[+=](map|seq|val|ali)$/) {
758 228         615 my $composer = "compose_$1";
759 228         608 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         473 return $node;
765             }
766             }
767             }
768              
769             sub compose_map {
770 57     57 0 122 my ($self, $event) = @_;
771 57         170 my $map = 'map'->new($event);;
772 57         113 my $events = $self->{events};
773 57         137 while (@$events) {
774 120 100       412 shift(@$events), return $map if $events->[0]{type} eq '-map';
775 63         152 my $k = $self->compose_node;
776 63         280 $k->{xkey} = 1;
777 63         139 my $v = $self->compose_node;
778 63         163 my $pair = 'pair'->new($k, $v);
779 63         132 $map->add($pair);
780             }
781 0         0 XXX $map, "problem composing map";
782             }
783              
784             sub compose_seq {
785 22     22 0 64 my ($self, $event) = @_;
786 22         90 my $seq = 'seq'->new($event);
787 22         49 my $events = $self->{events};
788 22         72 while (@$events) {
789 66 100       209 shift(@$events), return $seq if $events->[0]{type} eq '-seq';
790 44         116 my $elem = $self->compose_node;
791 44         107 $seq->add($elem);
792             }
793 0         0 XXX $seq, "problem composing seq";
794             }
795              
796             sub compose_val {
797 149     149 0 283 my ($self, $event) = @_;
798 149         343 '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   131 no warnings 'redefine';
  11         106  
  11         41026  
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 272 sub tag_node($n) {
  165         237  
  165         216  
827 165 50       359 if ($n->{ytag}) {
828 0 0       0 if ($n->{ytag} ne 'yamlscript') {
829 0         0 return 1;
830             }
831             }
832 165         330 $n = transform($n);
833 165 100       326 if (is_map($n)) {
    100          
834 55         108 for my $p (pairs($n)) {
835 57 50 66     132 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     211 $n->{ytag} //= 'module';
849             }
850             elsif (is_seq($n)) {
851 23         43 for my $e (@{$n->{elem}}) {
  23         74  
852 48         108 tag_node($e);
853             }
854 23         72 $n->{ytag} = 'do';
855             }
856             else {
857 87         167 tag_val($n);
858             }
859              
860 164         619 1;
861             }
862              
863 165     165 0 218 sub transform($n) {
  165         224  
  165         213  
864 165 100       333 if (is_map($n)) {
865 55         142 for my $p (pairs($n)) {
866 57         164 my ($k, $v) = @$p;
867              
868             $k->{text} =
869             "$k" eq '???' ? 'cond' :
870             "$k" eq '^^^' ? 'recur' :
871 57 50       154 $k->{text};
    100          
872              
873 57 100 100     114 if ("$k" eq 'cond' and is_map($v)) {
874             $p->[1] = bless {
875             elem => [
876 4         7 map { delete($_->{xkey}); $_ }
  4         13  
877 1         7 map { @$_ } @{$v->{pair}}
  2         5  
  1         4  
878             ],
879             }, 'seq';
880             }
881             }
882             }
883              
884 165         313 return $n;
885             }
886              
887 87     87 0 114 sub tag_val($n) {
  87         145  
  87         129  
888 87 50 66     162 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     24 ($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     181 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 64 sub tag_call($p) {
  36         60  
  36         48  
904 36         68 my ($k, $v) = @$p;
905 36 50       273 if ($k =~ /^$sym($bp?)$/) {
906 36         116 my $args = $1;
907             $k->{ytag} =
908 36 100       67 "$k" eq 'use'
909             ? "$k" :'call';
910              
911             # Empty (null) value
912 36 100 100     110 if (is_plain($v) and text($v) eq '') {
913 3 100       15 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 133 sub tag_catch($n) {
  57         87  
  57         77  
922 57 50       1377 $n->{ytag} = 'catch' if $n =~ /^catch\($sym\)$/;
923             }
924              
925 50     50 0 96 sub tag_def($p) {
  50         87  
  50         64  
926 50         106 my ($k, $v) = @$p;
927 50 100       932 return unless $k =~ /^$sym\s*=$/;
928 6         23 $k->{ytag} = 'def';
929 6         23 tag_node($v);
930             }
931              
932 56     56 0 84 sub tag_defn($p) {
  56         89  
  56         77  
933 56         123 my ($k, $v) = @$p;
934 56 100       626 return unless $k =~ /^(?:defn|defmacro)\s+$sym$bp$/;
935 6         36 $k->{ytag} = 'defn';
936 6         17 tag_node($v);
937             }
938              
939 57     57 0 107 sub tag_defn_multi($p) {
  57         80  
  57         82  
940 57         138 my ($k, $v) = @$p;
941 57 100 66     755 return unless $k =~ /^(?:defn|defmacro)\s+$sym$/ and is_map($v);
942 1         4 for my $p (pairs($v)) {
943 4 50       58 return unless $p->[0] =~ /^$bp$/;
944             }
945 1         5 $k->{ytag} = 'defn_multi';
946 1         3 for my $p (pairs($v)) {
947 4         11 my ($k, $v) = @$p;
948 4         8 tag_node($v);
949             }
950 1         5 return 1;
951             }
952              
953 44     44 0 75 sub tag_if($p) {
  44         71  
  44         63  
954 44         87 my ($k, $v) = @$p;
955 44 100       86 return unless $k =~ /^if +\S/;
956 2         9 $k->{ytag} = 'if';
957 2         7 tag_node($v);
958             }
959              
960 2     2 0 5 sub tag_istr($n) {
  2         4  
  2         4  
961 2 50       154 $n->{ytag} = 'istr' if $n =~ /(\$$sym|\$\()/;
962             }
963              
964 42     42 0 69 sub tag_fn($p) {
  42         57  
  42         55  
965 42         77 my ($k, $v) = @$p;
966 42 100       290 return unless $k =~ /^fn\s+$bp$/;
967 2         15 $k->{ytag} = 'fn';
968 2         10 tag_node($v);
969             }
970              
971 40     40 0 77 sub tag_let($n) {
  40         60  
  40         55  
972 40 50       234 $n->{ytag} = 'let1' if $n =~ /^let$/;
973             }
974              
975 40     40 0 66 sub tag_loop($p) {
  40         54  
  40         52  
976 40         72 my ($k, $v) = @$p;
977 40 100       74 return unless $k =~ /^loop +\S/;
978 1         3 $k->{ytag} = 'loop';
979 1         6 tag_node($v);
980             }
981              
982 85     85 0 115 sub tag_scalar($n) {
  85         119  
  85         110  
983 85         137 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       176 /^$sym$/ ? do {
    50          
    50          
    50          
    100          
    50          
991 27         79 $n->{text} =~ s/::/./g;
992 27         147 'sym';
993             } :
994             return;
995             }
996              
997 2     2 0 5 sub tag_str($n) {
  2         4  
  2         4  
998 2         8 $n->{ytag} = 'str';
999             }
1000              
1001 39     39 0 69 sub tag_try($n) {
  39         63  
  39         60  
1002 39 50       199 $n->{ytag} = 'try' if $n =~ /^try$/;
1003             }
1004              
1005 39     39 0 65 sub tag_when($p) {
  39         56  
  39         56  
1006 39         79 my ($k, $v) = @$p;
1007 39 100       71 return unless $k =~ /(?:\)|. )[?|]$/;
1008 3         8 $k->{ytag} = 'when';
1009 3         7 tag_node($v);
1010             }
1011              
1012 39     39 0 69 sub tag_ysexpr($n) {
  39         63  
  39         50  
1013 39         103 $n->{text} =~ s/^\.(?!\d)//;
1014 39         97 $n->{ytag} = 'ysexpr';
1015             }
1016              
1017             #------------------------------------------------------------------------------
1018             # Event and Node Classes
1019             #------------------------------------------------------------------------------
1020             {
1021             package event;
1022             sub new {
1023 572     572   1017 my ($class, $line) = @_;
1024 572         880 chomp $line;
1025 572         886 my $self = bless {}, $class;
1026 572         1758 @{$self}{@event_keys} = split /\t/, $line;
  572         2595  
1027 572         1765 return $self;
1028             }
1029             }
1030              
1031             {
1032             package pair;
1033             sub new {
1034 63     63   132 my ($class, $k, $v) = @_;
1035 63         154 bless [$k, $v], $class;
1036             }
1037 57     57   71 sub key($p) { $p->[0] }
  57         82  
  57         81  
  57         129  
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   128 my ($class, $event, @pairs) = @_;
1045 57         161 my $self = bless {
1046             pair => [@pairs],
1047             }, $class;
1048             $refs{$event->{anch}} = $self
1049 57 50       184 if $event->{anch} ne '-';
1050 57         267 $events{Scalar::Util::refaddr($self)} = $event;
1051 57         122 return $self;
1052             }
1053             sub add {
1054 63     63   116 my ($self, $pair) = @_;
1055 63         85 push @{$self->{pair}}, $pair;
  63         213  
1056             }
1057 171     171   670 sub pair { $_[0]->{pair} }
1058             }
1059              
1060             {
1061             package seq;
1062             sub new {
1063 57     57   136 my ($class, $event, @elems) = @_;
1064 57         170 my $self = bless {
1065             elem => [@elems],
1066             }, $class;
1067 57 50       143 if ($event) {
1068             $refs{$event->{anch}} = $self
1069 57 50       163 if $event->{anch} ne '-';
1070 57         266 $events{Scalar::Util::refaddr($self)} = $event;
1071             }
1072 57         126 return $self;
1073             }
1074             sub add {
1075 44     44   94 my ($self, $value) = @_;
1076 44         61 push @{$self->{elem}}, $value;
  44         105  
1077 44         105 return $self;
1078             }
1079 82     82   291 sub elem { $_[0]->{elem} }
1080             }
1081              
1082             {
1083             package val;
1084 11     11   114 use overload '""' => sub { $_[0]->{text} };
  11     1178   23  
  11         152  
  1178         24683  
1085             my %escapes = (
1086             'n' => "\n",
1087             't' => "\t",
1088             '\\' => '\\',
1089             '"' => '"',
1090             );
1091             sub new {
1092 183     183   380 my ($class, $event, $text) = @_;
1093 183   100     806 $text //= $event->{valu} // '';
      100        
1094 183         337 $text =~ s/\\([nt\\\"])/$escapes{$1}/g;
1095 183         464 my $self = bless {
1096             text => $text,
1097             }, $class;
1098 183 100       391 if ($event) {
1099 149         261 delete $event->{valu};
1100             $refs{$event->{anch}} = $self
1101 149 50       336 if $event->{anch} ne '-';
1102 149         505 $events{Scalar::Util::refaddr($self)} = $event;
1103             }
1104 183         375 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;