3
# Ruby subset extended from the `rubyish` example, as introduced in the
4
# Edument Rakudo and NQP internals course.
6
class RubyishClassHOW {
10
method new_type(:$name!) {
11
nqp::newtype(self.new(:$name), 'HashAttrStore')
14
method add_method($obj, $name, $code) {
15
if nqp::existskey(%!methods, $name) {
16
nqp::die("This class already has a method named " ~ $name);
18
%!methods{$name} := $code;
21
method find_method($obj, $name) {
22
%!methods{$name} // nqp::null();
26
grammar Rubyish::Grammar is HLL::Grammar {
29
:my $*CUR_BLOCK := QAST::Block.new(QAST::Stmts.new());
30
:my $*TOP_BLOCK := $*CUR_BLOCK;
31
:my $*CLASS_BLOCK := $*CUR_BLOCK;
32
:my $*IN_TEMPLATE := 0;
34
:my %*SYM := self.sym-init();
36
|| <.panic('Syntax error')>
39
token continuation { \\ \n }
40
rule separator { ';' | \n <!after continuation> }
41
token template-chunk { [<tmpl-unesc>|<tmpl-hdr>] ~ [<tmpl-esc>|$] <template-nibble>* }
42
proto token template-nibble {*}
43
token template-nibble:sym<interp> { <interp> }
44
token template-nibble:sym<stray-tag> { [<.tmpl-unesc>|<.tmpl-hdr>] <.panic("Stray tag, e.g. '%>' or '<?rbi?>'")> }
45
token template-nibble:sym<plain-text> { [<!before [<tmpl-esc>|'#{'|$]> .]+ }
47
token tmpl-hdr {'<?rbi?>' \h* \n? <?{$*IN_TEMPLATE := 1}>}
48
token tmpl-esc {\h* '<%'
49
[<?{$*IN_TEMPLATE}> || <.panic('Template directive precedes "<?rbi?>"')>]
51
token tmpl-unesc { '%>' \h* \n?
52
[<?{$*IN_TEMPLATE}> || <.panic('Template directive precedes "<?rbi?>"')>]
56
[ <stmt=.stmtish>? ] *%% [<.separator>|<stmt=.template-chunk>]
60
<stmt> [:s <modifier> :s <EXPR>]?
63
token modifier {if|unless|while|until}
68
:my %sym-save := self.hcopy(%*SYM);
70
'def' ~ 'end' <defbody>
72
<?{%*SYM := self.hcopy(%sym-save);1}>
76
:my $*CUR_BLOCK := QAST::Block.new(QAST::Stmts.new());
77
<operation> ['(' ~ ')' <signature>]? <separator>?
81
token comma { :s [','|'=>'] :s }
84
:my $*LINE_SPAN := 1; <param>* %% <comma>
87
token param { <ident> }
89
token stmt:sym<class> {
92
:my %sym-save := self.hcopy(%*SYM);
94
[<sym> \h+] ~ [\h* 'end'] <classbody>
96
<?{%*SYM := self.hcopy(%sym-save);1}>
100
:my $*CUR_BLOCK := QAST::Block.new(QAST::Stmts.new());
101
:my $*CLASS_BLOCK := $*CUR_BLOCK;
106
token stmt:sym<EXPR> { <EXPR> }
107
token term:sym<assign> { <var> \h* <OPER=infix> '=' \h* <EXPR> }
109
token term:sym<call> {
111
<operation> ['(' ~ ')' <call-args=.paren-args>?
112
| \h* <call-args>? <?{%*SYM{~$<operation>} eq 'def'}> ]
115
token term:sym<nqp-op> {
116
'nqp:'':'?<ident> ['(' ~ ')' <call-args=.paren-args>? | <call-args>? ]
119
token term:sym<quote-words> {
120
\% w <?before [.]> <quote_EXPR: ':q', ':w'>
123
token call-args { :s <EXPR>+ % <comma> }
124
token paren-args {:my $*LINE_SPAN := 1; <call-args> }
126
token operation {<ident>[\!|\?]?}
128
token term:sym<new> {
129
['new' \h+ :s <ident> | <ident> '.' 'new'] ['(' ~ ')' <call-args=.paren-args>?]?
132
token term:sym<lambda-call> {
133
<ident> '.' 'call' ['(' ~ ')' <call-args=.paren-args>?]?
137
:my $*MAYBE_DECL := 0;
138
[$<sigil>=[\$|\@\@?]|<!keyword>]
140
[ <?before \h* '=' [\w | \h+ || <EXPR>] { $*MAYBE_DECL := 1 }> || <?> ]
143
token term:sym<var> { <var> }
145
token term:sym<value> { \+? <value> }
147
proto token value {*}
148
token value:sym<string> {<strings>}
149
token strings {<string> [\h*<strings>]?}
150
token string { <?[']> <quote_EXPR: ':q'>
151
| <?["]> <quote_EXPR: ':qq'>
152
| \%[ q <?before [.]> <quote_EXPR: ':q'>
153
| Q <?before [.]> <quote_EXPR: ':qq'>
158
:my $*LINE_SPAN := 1;
162
token value:sym<integer> { \+? \d+ }
163
token value:sym<float> { \+? \d* '.' \d+ }
164
token value:sym<array> {'[' ~ ']' <paren-list> }
165
token value:sym<hash> {'{' ~ '}' <paren-list> }
166
token value:sym<nil> { <sym> }
167
token value:sym<true> { <sym> }
168
token value:sym<false> { <sym> }
171
token interp { '#{' ~ '}' [ :s <stmt> :s
172
|| <panic('string interpolation error')>]
174
token quote_escape:sym<#{ }> { <interp> }
178
[ BEGIN | class | ensure | nil | new | when
179
| END | def | false | not | super | while
180
| alias | defined | for | or | then | yield
181
| and | do | if | redo | true
182
| begin | else | in | rescue | undef
183
| break | elsif | module | retry | unless
184
| case | end | next | return | until
188
proto token comment {*}
189
token comment:sym<line> { '#' [<?{!$*IN_TEMPLATE}> \N* || [<!before <tmpl-unesc>>\N]*] }
190
token comment:sym<podish> {[^^'=begin'\n] ~ [^^'=end'\n ] .*?}
191
token ws { <!ww> [\h | <.comment> | <.continuation> | <?{$*LINE_SPAN}> \n]* }
193
# Operator precedence levels
195
Rubyish::Grammar.O(':prec<y=>, :assoc<unary>', '%unary');
196
Rubyish::Grammar.O(':prec<w=>, :assoc<left>', '%exponentiation');
197
Rubyish::Grammar.O(':prec<u=>, :assoc<left>', '%multiplicative');
198
Rubyish::Grammar.O(':prec<r=>, :assoc<left>', '%concatenation');
199
Rubyish::Grammar.O(':prec<t=>, :assoc<left>', '%additive');
200
Rubyish::Grammar.O(':prec<j=>, :assoc<right>', '%assignment');
201
Rubyish::Grammar.O(':prec<l=>, :assoc<left>', '%tight_and');
202
Rubyish::Grammar.O(':prec<k=>, :assoc<left>', '%tight_or');
203
Rubyish::Grammar.O(':prec<d=>, :assoc<left>', '%loose_and');
204
Rubyish::Grammar.O(':prec<c=>, :assoc<left>', '%loose_or');
207
# Operators - mostly stolen from NQP
208
token prefix:sym<-> { <sym> <O('%unary, :op<neg_n>')> }
210
token infix:sym<**> { <sym> <O('%exponentiation, :op<pow_n>')> }
212
token infix:sym<~> { <sym> <O('%concatenation , :op<concat>')> }
213
token infix:sym<*> { <sym> <O('%multiplicative, :op<mul_n>')> }
214
token infix:sym</> { <sym> <O('%multiplicative, :op<div_n>')> }
215
token infix:sym<%> { <sym><![>]> <O('%multiplicative, :op<mod_n>')> }
217
token infix:sym<+> { <sym> <O('%additive, :op<add_n>')> }
218
token infix:sym<-> { <sym> <O('%additive, :op<sub_n>')> }
219
token infix:sym<=> { <sym><!before '>'> <O('%assignment, :op<bind>')> }
221
token infix:sym«==» { <sym> <O('%relational, :op<iseq_n>')> }
222
token infix:sym«!=» { <sym> <O('%relational, :op<isne_n>')> }
223
token infix:sym«<=» { <sym> <O('%relational, :op<isle_n>')> }
224
token infix:sym«>=» { <sym> <O('%relational, :op<isge_n>')> }
225
token infix:sym«<» { <sym> <O('%relational, :op<islt_n>')> }
226
token infix:sym«>» { <sym> <O('%relational, :op<isgt_n>')> }
227
token infix:sym«eq» { <sym> <O('%relational, :op<iseq_s>')> }
228
token infix:sym«ne» { <sym> <O('%relational, :op<isne_s>')> }
229
token infix:sym«le» { <sym> <O('%relational, :op<isle_s>')> }
230
token infix:sym«ge» { <sym> <O('%relational, :op<isge_s>')> }
231
token infix:sym«lt» { <sym> <O('%relational, :op<islt_s>')> }
232
token infix:sym«gt» { <sym> <O('%relational, :op<isgt_s>')> }
234
token infix:sym<&&> { <sym> <O('%tight_and, :op<if>')> }
235
token infix:sym<||> { <sym> <O('%tight_or, :op<unless>')> }
236
token infix:sym<//> { <sym> <O('%tight_or, :op<defor>')> }
238
token infix:sym<and> { <sym> <O('%loose_and, :op<if>')> }
239
token infix:sym<or> { <sym> <O('%loose_or, :op<unless>')> }
242
token circumfix:sym<( )> {'(' <.ws> <EXPR> ')' <O('%methodop')> }
245
token postfix:sym<.> {
246
'.' <ident> [ '(' ~ ')' <call-args=.paren-args>? ]?
250
# Array and hash indices
251
token postcircumfix:sym<[ ]> { '[' ~ ']' [ <EXPR> ] <O('%methodop')> }
252
token postcircumfix:sym<{ }> { '{' ~ '}' [ <EXPR> ] <O('%methodop')> }
253
token postcircumfix:sym<ang> {
254
<?[<]> <quote_EXPR: ':q'>
259
token infix:sym<? :> {
264
<O('%conditional, :reducecheck<ternary>, :op<if>')>
269
<EXPR> [ <separator> [:s 'then']? | 'then' | <?before <tmpl-unesc>>] <stmtlist>
273
if ~ 'end' [ :s <xblock> [<else=.elsif>|<else>]? ]
277
'elsif' ~ [<else=.elsif>|<else>]? <xblock>
285
<modifier> :s <EXPR> :s <do> ~ 'end' <stmtlist>
288
token stmt:sym<for> {
289
<sym> :s <ident> :s 'in' <EXPR> <do> ~ 'end' <stmtlist>
292
token do { <separator> [:s 'do']? | 'do' | <?before <tmpl-unesc>>}
294
token term:sym<code> {
295
'begin' ~ 'end' <stmtlist>
298
token term:sym<lambda> {
299
'lambda' :s ['{' ['|' ~ '|' <signature>]?] ~ '}' <stmtlist>
302
method builtin-init() {
313
my %builtins := self.builtin-init();
315
%sym{$_} := 'def' for %builtins;
321
%out{$_} := %in{$_} for %in;
326
class Rubyish::Actions is HLL::Actions {
329
$*CUR_BLOCK.push($<stmtlist>.ast);
333
method stmtlist($/) {
334
my $stmts := QAST::Stmts.new( :node($/) );
344
method template-chunk($/) {
345
my $text := QAST::Stmts.new( :node($/) );
346
$text.push( QAST::Op.new( :op<print>, $_.ast ) )
347
for $<template-nibble>;
352
method template-nibble:sym<interp>($/) {
356
method template-nibble:sym<plain-text>($/) {
357
make QAST::SVal.new( :value(~$/) );
362
?? QAST::Op.new( $<EXPR>.ast, $<stmt>.ast,
363
:op(~$<modifier>), :node($/) )
367
method term:sym<value>($/) { make $<value>.ast; }
369
# a few ops that map directly to Ruby built-ins
372
method term:sym<call>($/) {
373
my $name := ~$<operation>;
374
%builtins := Rubyish::Grammar.builtin-init()
375
unless %builtins<puts>;
376
my $op := %builtins{$name};
379
?? QAST::Op.new( :op($op) )
380
!! QAST::Op.new( :op('call'), :name($name) );
384
for $<call-args>.ast;
390
method term:sym<nqp-op>($/) {
392
my $call := QAST::Op.new( :op($op) );
396
for $<call-args>.ast;
402
method call-args($/) {
404
@args.push($_.ast) for $<EXPR>;
408
method paren-args($/) {
409
make $<call-args>.ast;
414
method term:sym<new>($/) {
417
my $tmp-sym := '$new' ~ (++$tmpsym) ~ '$';
419
my $init-call := QAST::Op.new( :op<callmethod>,
421
QAST::Var.new( :name($tmp-sym), :scope<lexical> )
426
for $<call-args>.ast;
431
# create the new object
432
QAST::Op.new( :op('bind'),
433
QAST::Var.new( :name($tmp-sym), :scope<lexical>, :decl<var>),
436
QAST::Var.new( :name('::' ~ ~$<ident>), :scope('lexical') )
440
# call initialize method, if available
441
QAST::Op.new( :op<if>,
442
QAST::Op.new( :op<can>,
443
QAST::Var.new( :name($tmp-sym), :scope<lexical> ),
444
QAST::SVal.new( :value<initialize> )),
448
# return the new object
449
QAST::Var.new( :name($tmp-sym), :scope<lexical> ),
453
method term:sym<lambda-call>($/) {
454
my $call := QAST::Op.new( :op<call>,
459
for $<call-args>.ast;
465
my $sigil := ~$<sigil>//'';
466
my $name := $sigil ~ $<ident>;
470
if $sigil eq '@' && $*IN_CLASS {
472
make QAST::Var.new( :scope('attribute'),
473
QAST::Var.new( :name('self'), :scope('lexical')),
474
QAST::SVal.new( :value($name) )
481
$block := $*CUR_BLOCK;
483
elsif $sigil eq '$' {
484
$block := $*TOP_BLOCK;
486
elsif $sigil eq '@' {
487
$block := $*CLASS_BLOCK;
489
elsif $sigil eq '@@' {
490
$block := $*CLASS_BLOCK;
494
nqp::die("unhandled sigil: $sigil");
497
my %sym := $block.symbol($name);
499
%*SYM{$name} := 'var';
500
$block.symbol($name, :declared(1), :scope('lexical') );
501
my $var := QAST::Var.new( :name($name), :scope('lexical'),
503
$block[0].push($var);
507
make QAST::Var.new( :name($name), :scope('lexical') );
511
method term:sym<var>($/) { make $<var>.ast }
513
method stmt:sym<def>($/) {
514
my $install := $<defbody>.ast;
515
$*CUR_BLOCK[0].push(QAST::Op.new(
517
QAST::Var.new( :name($install.name), :scope('lexical'), :decl('var') ),
520
%*SYM{$install.name} := 'def';
522
@*METHODS.push($install);
524
make QAST::Op.new( :op('null') );
528
$*CUR_BLOCK.name(~$<operation>);
530
for $<signature>.ast {
531
$*CUR_BLOCK[0].push($_);
532
$*CUR_BLOCK.symbol($_.name, :declared(1));
535
$*CUR_BLOCK.push($<stmtlist>.ast);
537
# it's a method, self will be automatically passed
538
$*CUR_BLOCK[0].unshift(QAST::Var.new(
539
:name('self'), :scope('lexical'), :decl('param')
541
$*CUR_BLOCK.symbol('self', :declared(1));
547
method signature($/) {
550
@params.push(QAST::Var.new(
551
:name(~$_), :scope('lexical'), :decl('param')
557
method stmt:sym<class>($/) {
558
my $body_block := $<classbody>.ast;
560
# Generate code to create the class.
561
my $class_stmts := QAST::Stmts.new( $body_block );
562
my $ins_name := '::' ~ $<classbody><ident>;
563
$class_stmts.push(QAST::Op.new(
565
QAST::Var.new( :name($ins_name), :scope('lexical'), :decl('var') ),
567
:op('callmethod'), :name('new_type'),
568
QAST::WVal.new( :value(RubyishClassHOW) ),
569
QAST::SVal.new( :value(~$<classbody><ident>), :named('name') ) )
573
my $class_var := QAST::Var.new( :name($ins_name), :scope('lexical') );
576
$class_stmts.push(QAST::Op.new(
577
:op('callmethod'), :name('add_method'),
578
QAST::Op.new( :op('how'), $class_var ),
580
QAST::SVal.new( :value($_.name) ),
581
QAST::BVal.new( :value($_) )));
586
method classbody($/) {
587
$*CUR_BLOCK.push($<stmtlist>.ast);
588
$*CUR_BLOCK.blocktype('immediate');
592
method stmt:sym<EXPR>($/) { make $<EXPR>.ast; }
594
method term:sym<assign>($/) {
595
my $op := $<OPER><O><op>;
596
make QAST::Op.new( :op('bind'),
598
QAST::Op.new( :op($op),
604
method value:sym<string>($/) {
610
?? QAST::Op.new( :op('concat'), $<string>.ast, $<strings>.ast)
615
make $<quote_EXPR>.ast;
618
method value:sym<integer>($/) {
619
make QAST::IVal.new( :value(+$/.Str) )
622
method value:sym<float>($/) {
623
make QAST::NVal.new( :value(+$/.Str) )
626
method paren-list($/) {
629
@list.push($_.ast) for $<EXPR>
634
method value:sym<array>($/) {
635
my $array := QAST::Op.new( :op<list> );
636
$array.push($_) for $<paren-list>.ast;
640
method term:sym<quote-words>($/) {
641
make $<quote_EXPR>.ast;
644
method value:sym<hash>($/) {
645
my $hash := QAST::Op.new( :op<hash> );
646
$hash.push($_) for $<paren-list>.ast;
650
method value:sym<nil>($/) {
651
make QAST::Op.new( :op<null> );
654
method value:sym<true>($/) {
655
make QAST::IVal.new( :value<1> );
658
method value:sym<false>($/) {
659
make QAST::IVal.new( :value<0> );
662
method interp($/) { make $<stmt>.ast }
663
method quote_escape:sym<#{ }>($/) { make $<interp>.ast }
664
method circumfix:sym<( )>($/) { make $<EXPR>.ast }
666
method postfix:sym<.>($/) {
667
my $meth_call := QAST::Op.new( :op('callmethod'), :name(~$<ident>) );
669
$meth_call.push($_) for $<call-args>.ast;
674
method postcircumfix:sym<[ ]>($/) {
675
make QAST::Var.new( :scope('positional'), $<EXPR>.ast );
678
method postcircumfix:sym<{ }>($/) {
679
make QAST::Var.new( :scope('associative'), $<EXPR>.ast );
682
method postcircumfix:sym<ang>($/) {
683
make QAST::Var.new( :scope('associative'), $<quote_EXPR>.ast );
686
# borrowed from NQP::Actions
688
make QAST::Op.new( $<EXPR>.ast, $<stmtlist>.ast, :op('if'), :node($/) );
691
method stmt:sym<if>($/) {
692
my $ast := $<xblock>.ast;
693
$ast.push( $<else>.ast )
700
my $ast := $<xblock>.ast;
701
$ast.push( $<else>.ast )
711
method stmt:sym<do>($/) {
712
make QAST::Op.new( $<EXPR>.ast, $<stmtlist>.ast, :op(~$<modifier>), :node($/) );
715
method stmt:sym<for>($/) {
717
my $block := QAST::Block.new(
718
QAST::Var.new( :name(~$<ident>), :scope('lexical'), :decl('param')),
721
make QAST::Op.new( $<EXPR>.ast, $block, :op('for'), :node($/) );
724
method term:sym<code>($/) {
725
make $<stmtlist>.ast;
728
method term:sym<lambda>($/) {
729
my $block := QAST::Block.new();
731
for $<signature>.ast {
733
$block.symbol($_.name, :declared(1));
736
$block.push($<stmtlist>.ast);
738
make QAST::Op.new(:op<takeclosure>,
745
class Rubyish::Compiler is HLL::Compiler {
749
my $comp := Rubyish::Compiler.new();
750
$comp.language('rubyish');
751
$comp.parsegrammar(Rubyish::Grammar);
752
$comp.parseactions(Rubyish::Actions);
753
$comp.command_line(@ARGS, :encoding('utf8'));