From f63749b8e2ea45aee3af0af4af4a2ca626dda6ad Mon Sep 17 00:00:00 2001 From: ab5tract Date: Mon, 9 Oct 2023 17:02:48 +0200 Subject: [PATCH] RakuAST: Implement FIRST phaser as an AST construct 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. --- src/Raku/ast/code.rakumod | 35 ++----------------- src/Raku/ast/statementprefixes.rakumod | 48 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/Raku/ast/code.rakumod b/src/Raku/ast/code.rakumod index 28566cba934..adc834304dd 100644 --- a/src/Raku/ast/code.rakumod +++ b/src/Raku/ast/code.rakumod @@ -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) @@ -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); 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; - $block := QAST::Stmts.new( - QAST::Op.new(:op, $var.decl_as('var'), - QAST::Stmts.new( - QAST::Op.new( - :op('p6setfirstflag'), - QAST::Var.new( - :value(nqp::getattr(self.meta-object, $Code, '$!do')), - :name, - :scope, - :decl)))), - $block, - QAST::Op.new(:op, $var).annotate_self('loop-already-block-first-phaser', $block)); - } $block } else { diff --git a/src/Raku/ast/statementprefixes.rakumod b/src/Raku/ast/statementprefixes.rakumod index 53cb2bab673..8781fa74bb9 100644 --- a/src/Raku/ast/statementprefixes.rakumod +++ b/src/Raku/ast/statementprefixes.rakumod @@ -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.