Skip to content

Commit

Permalink
RakuAST: Implement FIRST phaser as an AST construct
Browse files Browse the repository at this point in the history
It turns out that there is no need to even dip into QAST in order
to implement the `FIRST` phaser, which was particularly tricky
because it depended on so-called "extops" that are particularly
hard to arrange in pure QAST (`p6setfirstflag` and `p6takefirstflag`,
for the curious).

It turns out (nine++) that we can simply arrange for this construct
at an AST level, by emulating something like:

```
{
  state $triggered;
  unless $triggered {
    # ... the actual content of the FIRST phaser block
    $triggered = True
  }
}()
```

This feels like a much more natural implementation and it really
shows off what a difference RakuAST can already make to code generation.
  • Loading branch information
ab5tract authored and lizmat committed Oct 10, 2023
1 parent daee398 commit f63749b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 33 deletions.
35 changes: 2 additions & 33 deletions src/Raku/ast/code.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -730,13 +730,7 @@ class RakuAST::ScopePhaser {
for $!FIRST {
$first-setup.push($_.IMPL-CALLISH-QAST($context));
}
$qast[0].push(
QAST::Op.new(
:op('if'),
QAST::Op.new( :op('p6takefirstflag') ),
$first-setup
)
);
$qast[0].push: $first-setup;
}

my $phasers := nqp::istype(self, RakuAST::Code)
Expand Down Expand Up @@ -1108,33 +1102,8 @@ class RakuAST::Block
if $immediate {
# For now, assume we never need a code object for such a block. The
# closure clone is done for us by the QAST compiler.
# Aside from that, we need to change our plans if there is a FIRST phaser
# because it needs to be triggered manually. If you've passed :immediate and
# there are FIRST phasers, we are going to give you back this self-calling
# construct.
my $Code := self.get-implicit-lookups.AT-POS(0).compile-time-value;
my $has-first-phasers := self.has-phaser('FIRST');

my $blocktype := $has-first-phasers ?? 'declaration_static' !! 'immediate';
my $block := self.IMPL-QAST-FORM-BLOCK($context, :$blocktype);
my $block := self.IMPL-QAST-FORM-BLOCK($context, :blocktype<immediate>);
self.IMPL-LINK-META-OBJECT($context, $block);

if $has-first-phasers {
my $tmp := QAST::Node.unique('LOOP_BLOCK');
my $var := QAST::Var.new: :name($tmp), :scope<local>;
$block := QAST::Stmts.new(
QAST::Op.new(:op<bind>, $var.decl_as('var'),
QAST::Stmts.new(
QAST::Op.new(
:op('p6setfirstflag'),
QAST::Var.new(
:value(nqp::getattr(self.meta-object, $Code, '$!do')),
:name<loop-code-attribute-do>,
:scope<local>,
:decl<var>)))),
$block,
QAST::Op.new(:op<call>, $var).annotate_self('loop-already-block-first-phaser', $block));
}
$block
}
else {
Expand Down
48 changes: 48 additions & 0 deletions src/Raku/ast/statementprefixes.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,56 @@ class RakuAST::StatementPrefix::Phaser::Block
# The FIRST phaser.
class RakuAST::StatementPrefix::Phaser::First
is RakuAST::StatementPrefix::Phaser::Block
is RakuAST::BeginTime
{
method type() { "FIRST" }

method is-begin-performed-before-children { True }
method is-begin-performed-after-children { False }

method PERFORM-BEGIN(Resolver $resolver, RakuAST::IMPL::QASTContext $context) {
my $blorst := nqp::getattr(self, RakuAST::StatementPrefix, '$!blorst');

my $True := RakuAST::Term::Name.new(RakuAST::Name.from-identifier('True'));

my $attach-block := $resolver.find-attach-target('block');
my $trigger-name := QAST::Node.unique('!first_block_triggered');
my $trigger-var := RakuAST::VarDeclaration::Implicit::State.new: $trigger-name;
my $trigger-lookup := $trigger-var.generate-lookup;
$attach-block.add-generated-lexical-declaration($trigger-var);

unless nqp::istype($blorst, RakuAST::Block) {
$blorst := nqp::istype($blorst, RakuAST::Statement)
??
RakuAST::Block.new:
:body(RakuAST::Blockoid.new:
RakuAST::StatementList.new:
$blorst)
!!
RakuAST::Block.new:
:body(RakuAST::Blockoid.new:
RakuAST::StatementList.new:
RakuAST::Statement::Expression.new:
:expression($blorst));
}

$blorst.body.statement-list.add-statement:
RakuAST::Statement::Expression.new:
:expression(RakuAST::ApplyInfix.new:
:infix(RakuAST::Assignment.new(:item)),
:left($trigger-lookup),
:right($True));

my $wrapper-block :=
RakuAST::Block.new:
:body(RakuAST::Blockoid.new:
RakuAST::StatementList.new:
RakuAST::Statement::Unless.new:
:condition($trigger-lookup),
:body($blorst));

nqp::bindattr(self, RakuAST::StatementPrefix, '$!blorst', $wrapper-block);
}
}

# The NEXT phaser.
Expand Down

0 comments on commit f63749b

Please sign in to comment.