1#! nqp
2# Copyright (C) 2009-2010, Parrot Foundation.
3
4class Ops::Compiler::Actions is HLL::Actions;
5
6our $OP;
7our $OPLIB;
8
9INIT {
10    pir::load_bytecode("nqp-setting.pbc");
11    $OPLIB := 0;
12}
13
14method TOP($/) {
15    make $<body>.ast;
16}
17
18method body($/) {
19    my $past := PAST::Stmts.new(
20        :node($/)
21    );
22
23    $past<preamble> := PAST::Stmts.new(
24        :node($/)
25    );
26    $past<ops> := PAST::Stmts.new(
27        :node($/)
28    );
29
30    for $<preamble> {
31        $past<preamble>.push($_<preamble_guts>);
32    }
33
34    for $<op> {
35        my $ops := $_.ast;
36        my $op_skip_table;
37        if $OPLIB {
38            $op_skip_table := $OPLIB.op_skip_table;
39        }
40        for @($ops) -> $op {
41            if $OPLIB && !$op_skip_table.exists($op.full_name) || !$OPLIB {
42                $past<ops>.push($op);
43            }
44        }
45    }
46
47    make $past;
48}
49
50method preamble($/) {
51    make PAST::Op.new(
52        :node($/),
53        :pasttype('preamble'),
54        ~$<preamble_guts>
55    );
56}
57
58method op ($/, $key?) {
59
60    if $key eq 'start' {
61        # Handling flags.
62        my %flags := hash();
63        for $<op_flag> {
64            %flags{~$_<identifier>} := 1;
65        }
66
67        my @args      := @($<signature>.ast);
68        my @norm_args := normalize_args(@args);
69
70        $OP := Ops::Op.new(
71            :name(~$<op_name>),
72        );
73
74        if ~$<op_name> eq 'runinterp' {
75            $OP.add_jump('PARROT_JUMP_RELATIVE');
76        }
77
78        $OP<flags> := %flags;
79        $OP<args>  := @args;
80        $OP<type>  := ~$<op_type>;
81        $OP<normalized_args> := @norm_args;
82    }
83    else {
84        # Handle op body
85        $OP.push($<op_body>.ast);
86
87        if !$OP<flags><flow> {
88            $OP[0].push(self.make_write_barrier) if $OP.need_write_barrier;
89
90            my $goto_next := PAST::Op.new(
91                :pasttype('macro'),
92                :name('goto_offset'),
93                self.opsize,
94            );
95
96            $OP[0].push($goto_next);
97        }
98
99        my $past := PAST::Stmts.new(
100            :node($/)
101        );
102
103        # We have to clone @norm_args. Otherwise it will be destroyed...
104        my @variants  := expand_args(pir::clone__PP($OP<normalized_args>));
105        if @variants {
106            for @variants {
107                my $new_op := pir::clone__PP($OP);
108                $new_op<arg_types> := $_;
109                $past.push($new_op);
110            }
111        }
112        else {
113            $past.push($OP);
114        }
115
116        make $past;
117    }
118}
119
120# Normalize args
121# For each arg produce LoL of all available variants
122# E.g. "in" will produce "i" and "ic" variants
123#
124# type one of <i p s n>
125# direction one of <i o io>
126# is_label one of <0 1>
127
128sub normalize_args(@args) {
129    my @result;
130    for @args -> $arg {
131        my $res := PAST::Var.new(
132            :isdecl(1)
133        );
134
135        if $arg<type> eq 'LABEL' {
136            $res<type>     := 'i';
137            $res<is_label> := 1;
138        }
139        else {
140            $res<is_label> := 0;
141        }
142
143        if $arg<type> eq 'INTKEY' {
144            $res<type> := 'ki';
145        }
146        elsif $arg<type> ne 'LABEL' {
147            $res<type> := lc(substr($arg<type>, 0, 1));
148        }
149
150        my $use := $arg<direction>;
151
152        if $use eq 'in' {
153            $res<variant>   := $res<type> ~ "c";
154            $res<direction> := 'i';
155        }
156        elsif $use eq 'invar' {
157            $res<direction> := 'i';
158        }
159        elsif $use eq 'inconst' {
160            $res<type>      := $res<type> ~ "c";
161            $res<direction> := 'i';
162        }
163        elsif $use eq 'inout' {
164            $res<direction> := 'io';
165        }
166        else {
167            $res<direction> := 'o';
168        }
169
170        @result.push($res);
171    }
172    @result;
173}
174
175=begin
176
177=item C<expand_args(@args)>
178
179Given an argument list, returns a list of all the possible argument
180combinations.
181
182=end
183sub expand_args(@args) {
184
185    return list() unless @args;
186
187    my $arg := @args.shift;
188
189    my @var := list($arg<type>);
190    if $arg<variant> {
191        @var.push($arg<variant>);
192    }
193
194    my @list := expand_args(@args);
195    unless +@list {
196        return @var;
197    }
198
199    my @results;
200    for @list -> $l {
201        for @var -> $v {
202            # NQP can't handle it automagically. So wrap $l into list.
203            my @l := pir::does__IPS($l, 'array') ?? $l !! list($l);
204            @results.push(list($v, |@l));
205        }
206    }
207
208    @results;
209}
210
211
212method signature($/) {
213    my $past := PAST::Stmts.new(
214        :node($/)
215    );
216
217    for $<op_param> {
218        $past.push($_.ast);
219    }
220
221    make $past;
222}
223
224method op_param($/) {
225    my $past := PAST::Var.new(
226        :node($/),
227        :isdecl(1)
228    );
229
230    # We have to store 2 "types". Just set 2 properties on Var for now
231    $past<direction> := ~$<op_param_direction>;
232    $past<type>      := ~$<op_param_type>;
233
234    make $past;
235}
236
237method op_body($/) {
238    make $<blockoid>.ast;
239}
240
241method op_macro:sym<expr offset>($/) {
242    make PAST::Op.new(
243        :pasttype<macro>,
244        :name<expr_offset>,
245        $<arg>.ast,
246    );
247}
248
249method op_macro:sym<goto offset>($/) {
250    $OP.add_jump('PARROT_JUMP_RELATIVE');
251
252    my $past := PAST::Op.new(
253        :pasttype<macro>,
254        :name<goto_offset>,
255        $<arg>.ast,
256    );
257
258    if $OP.need_write_barrier {
259        $past := PAST::Block.new(
260            self.make_write_barrier,
261            $past,
262        );
263    }
264
265    make $past;
266}
267
268method op_macro:sym<expr address>($/) {
269    make PAST::Op.new(
270        :pasttype<macro>,
271        :name<expr_address>,
272        $<arg>.ast,
273    );
274}
275
276method op_macro:sym<goto address>($/) {
277    my $past := PAST::Op.new(
278        :pasttype<macro>,
279        :name<goto_address>,
280        $<arg>.ast,
281    );
282
283    if $OP.need_write_barrier {
284        $past := PAST::Block.new(
285            self.make_write_barrier,
286            $past,
287        );
288    }
289
290    make $past;
291}
292
293method op_macro:sym<expr next>($/) {
294    make PAST::Op.new(
295        :pasttype<macro>,
296        :name<expr_offset>,
297        self.opsize,
298    );
299}
300
301method op_macro:sym<goto next>($/) {
302    $OP.add_jump('PARROT_JUMP_RELATIVE');
303
304    my $past := PAST::Op.new(
305        :pasttype<macro>,
306        :name<goto_offset>,
307        self.opsize,
308    );
309
310    if $OP.need_write_barrier {
311        $past := PAST::Block.new(
312            self.make_write_barrier,
313            $past,
314        );
315    }
316
317    make $past;
318}
319
320
321method op_macro:sym<restart next> ($/) {
322    #say('# op_macro');
323    # restart NEXT()      -> restart_offset(opsize()); goto_address(0)
324    my $past := PAST::Stmts.new(
325        PAST::Op.new(
326            :pasttype<macro>,
327            :name<restart_offset>,
328            self.opsize,
329        ),
330        PAST::Op.new(
331            :pasttype<macro>,
332            :name<goto_address>,
333            PAST::Val.new(
334                :value<0>
335            )
336        ),
337    );
338
339    $past.unshift(self.make_write_barrier) if $OP.need_write_barrier;
340
341    make $past;
342}
343
344method blockoid ($/) {
345    my $past := PAST::Block.new(:node($/));
346
347    $past.push($_) for @($<mixed_content>.ast);
348
349    make $past;
350}
351
352method mixed_content ($/) {
353    my $past := PAST::Stmts.new(:node($/));
354
355    @($_.ast).map(-> $_ { $past.push($_) }) for $<declarator>;
356    $past.push($_) for @($<statement_list>.ast);
357
358    make $past;
359}
360
361method declarator ($/) {
362    my $past := PAST::Stmts.new(:node($/));
363    for $<declarator_name> {
364        my $decl := PAST::Var.new(
365            :node($_),
366            :isdecl(1),
367            :name(~$_<variable>),
368            :vivibase(~$<type_declarator>),
369        );
370
371        $decl.viviself($_<EXPR>[0].ast) if $_<EXPR>[0];
372
373        $decl<array_size> := ~$_<array_size><VALUE> if $_<array_size>;
374        $decl<pointer>    := $_<pointer>.join('');
375        $past.push($decl);
376    }
377
378    make $past;
379}
380
381method statement_list ($/) {
382    my $past := PAST::Stmts.new(:node($/));
383
384    $past.push($_.ast) for $<labeled_statement>;
385
386    # Avoid wrapping single blockoid into Stmts.
387    make (+@($past) == 1) && ($past[0] ~~ PAST::Block)
388        ?? $past[0]
389        !! $past;
390}
391
392method labeled_statement ($/) {
393    # FIXME!!!
394    my $past := $<statement>
395                ?? $<statement>.ast
396                !! PAST::Op.new();
397
398    # FIXME!!! We need some semantics here.
399    $past<label> := ~$<label> if $<label>;
400
401    make $past;
402}
403
404method statement ($/) {
405    my $past;
406
407    if $<statement_control> {
408        $past := $<statement_control>.ast;
409    }
410    elsif $<blockoid> {
411        $past := $<blockoid>.ast;
412    }
413    elsif $<EXPR> {
414        $past := $<EXPR>.ast;
415    }
416    elsif $<c_macro> {
417        $past := $<c_macro>.ast;
418    }
419    else {
420        $/.CURSOR.panic("Unknown content in statement");
421    }
422
423    make $past;
424}
425
426method c_macro:sym<define> ($/) {
427    my $past := PAST::Op.new(
428        :pasttype<macro_define>,
429        $<name>,
430    );
431
432    $past<macro_args> := $<c_macro_args>[0].ast if $<c_macro_args>;
433    $past<body>       := $<body>[0].ast         if $<body>;
434
435    make $past;
436}
437
438method c_macro:sym<if> ($/) {
439    my $past := PAST::Op.new(
440        :pasttype<macro_if>,
441
442        ~$<condition>,  # FIXME! We have to parse condition somehow.
443        $<then>.ast,
444    );
445
446    $past.push($<else>[0].ast) if $<else>;
447
448    make $past;
449}
450
451method c_macro:sym<ifdef> ($/) {
452    my $past := PAST::Op.new(
453        :pasttype<macro_if>,
454
455        'defined(' ~ ~$<name> ~ ')',  # FIXME! We have to parse condition somehow.
456        $<then>.ast,
457    );
458
459    $past.push($<else>[0].ast) if $<else>;
460
461    make $past;
462}
463
464method term:sym<concatenate_strings> ($/) {
465    make ~$<identifier> ~ ' ' ~ ~$<quote>;
466}
467
468method term:sym<identifier> ($/) {
469    # XXX Type vs Variable
470    make PAST::Var.new(
471        :name(~$/),
472    );
473}
474
475method term:sym<call> ($/) {
476    my $past := PAST::Op.new(
477        :pasttype('call'),
478        :name(~$<identifier>),
479    );
480
481    if ($<arglist><arg>) {
482        $past.push($_.ast) for $<arglist><arg>;
483    }
484
485    make $past;
486}
487
488method arg ($/) {
489    make $<type_declarator>
490        ?? ~$<type_declarator>
491        !! $<EXPR>.ast;
492}
493
494method term:sym<reg> ($/) {
495    make PAST::Var.new(
496        :name(+$<num>),
497        :node($/),
498        :scope('register'), # Special scope
499    );
500}
501
502method term:sym<macro> ($/) {
503    make $<op_macro>.ast;
504}
505
506method term:sym<int> ($/) {
507    # TODO Handle type
508    make PAST::Val.new(
509        :value(~$/),
510        :returns<int>
511    );
512}
513
514method term:sym<str> ($/) {
515    make PAST::Val.new(
516        :value(~$<quote>),
517        :returns<string>
518    );
519}
520
521method term:sym<float_constant_long> ($/) { # longer to work-around lack of LTM
522    make PAST::Val.new(
523        :value(~$/[0]),
524        :returns<float>
525    );
526}
527
528method infix:sym<?:> ($/) {
529    my $past := PAST::Op.new(
530        :pasttype<if>,
531    );
532    # Override to emit ternary ops in .to_c
533    $past<ternary> := 1;
534    make $past;
535}
536
537method statement_control:sym<if> ($/) {
538    my $past := PAST::Op.new(
539        :pasttype<if>,
540
541        $<EXPR>.ast,
542        $<then>.ast,
543    );
544
545    $past.push($<else>[0].ast) if $<else>;
546
547    make $past;
548}
549
550method statement_control:sym<while> ($/) {
551    my $past := PAST::Op.new(
552        :pasttype<while>,
553
554        $<condition>.ast,
555        $<statement_list>.ast,
556    );
557
558    make $past;
559}
560
561method statement_control:sym<do-while> ($/) {
562    my $past := PAST::Op.new(
563        :pasttype<do-while>,
564
565        $<blockoid>.ast,
566        $<condition>.ast,
567    );
568
569    make $past;
570}
571
572method statement_control:sym<for> ($/) {
573    my $past := PAST::Op.new(
574        :pasttype<for>,
575
576        $<init> ?? $<init>[0].ast !! undef,
577        $<test> ?? $<test>[0].ast !! undef,
578        $<step> ?? $<step>[0].ast !! undef,
579        $<statement_list>.ast,
580    );
581
582    make $past;
583}
584
585# Not real "C" switch. Just close enough
586method statement_control:sym<switch> ($/) {
587    my $past := PAST::Op.new(
588        :pasttype<switch>,
589        $<test>.ast,
590        $<statement_list>.ast,
591    );
592    make $past;
593}
594
595method statement_control:sym<break> ($/) {
596    my $past := PAST::Op.new();
597    $past<control> := 'break';
598    make $past;
599}
600
601method statement_control:sym<continue> ($/) {
602    my $past := PAST::Op.new();
603    $past<control> := 'continue';
604    make $past;
605}
606
607method statement_or_block ($/) {
608    $<labeled_statement>
609        ?? make PAST::Block.new(
610               $<labeled_statement>.ast
611           )
612        !! make $<blockoid>.ast
613}
614
615method circumfix:sym<( )> ($/) {
616    my $past := $<EXPR>.ast;
617
618    # Indicate that we need wrapping.
619    $past<wrap> := 1;
620
621    make $past;
622}
623
624method postcircumfix:sym<[ ]> ($/) {
625    make PAST::Var.new(
626        $<EXPR>.ast,
627        :scope('keyed'),
628    );
629}
630
631
632# For casting we just set "returns" of EXPR.
633method prefix:sym<( )> ($/) {
634    make PAST::Op.new(
635        :returns(~$<type_declarator>),
636    );
637}
638
639# Helper method for generating PAST::Val with opsize
640method opsize () {
641    make PAST::Val.new(
642        :value($OP.size),
643        :returns('int'),
644    );
645}
646
647method make_write_barrier () {
648    make PAST::Op.new(
649        :pasttype<call>,
650        :name<PARROT_GC_WRITE_BARRIER>,
651        PAST::Var.new(
652            :name<interp>
653        ),
654        PAST::Op.new(
655            :pasttype<call>,
656            :name<CURRENT_CONTEXT>,
657            PAST::Var.new(
658                :name<interp>
659            )
660        )
661    );
662}
663
664# Local Variables:
665#   mode: perl6
666#   fill-column: 100
667# End:
668# vim: expandtab ft=perl6 shiftwidth=4:
669