Skip to content

Commit

Permalink
Support signature declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
niner committed May 17, 2022
1 parent f5cfaa0 commit a2c6bcb
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 46 deletions.
9 changes: 9 additions & 0 deletions src/Raku/Actions.nqp
Expand Up @@ -1103,6 +1103,15 @@ class Raku::Actions is HLL::Actions does Raku::CommonActions {
if $<variable_declarator> {
self.attach: $/, $<variable_declarator>.ast;
}
elsif $<signature> {
my str $scope := $*SCOPE;
my $type := $*OFTYPE ?? $*OFTYPE.ast !! self.r('Type');
my $initializer := $<initializer>
?? $<initializer>.ast
!! self.r('Initializer');
self.attach: $/, self.r('VarDeclaration', 'Signature').new:
:signature($<signature>.ast), :$scope, :$type, :$initializer;
}
elsif $<routine_declarator> {
self.attach: $/, $<routine_declarator>.ast;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Raku/Grammar.nqp
Expand Up @@ -1630,7 +1630,7 @@ grammar Raku::Grammar is HLL::Grammar does Raku::Common {
| $<sigil>=['$'] $<desigilname>=[<[/_!¢]>]
| <sigil> $<index>=[\d+]
| <sigil> <?[<]> <postcircumfix>
| <?before <.sigil> <.?[ ( [ { ]>> <!RESTRICTED> <contextualizer>
| <?before <.sigil> <.?[ ( [ { ]>> <!RESTRICTED> <?{ !$*IN_DECL }> <contextualizer>
| {} <sigil> <!{ $*QSIGIL }> <?MARKER('baresigil')> # try last, to allow sublanguages to redefine sigils (like & in regex)
]
{ $*LEFTSIGIL := nqp::substr(self.orig(), self.from, 1) unless $*LEFTSIGIL }
Expand Down Expand Up @@ -1784,6 +1784,7 @@ grammar Raku::Grammar is HLL::Grammar does Raku::Common {
| '\\' <defterm>
[ <.ws> <term_init=initializer> || <.typed_panic: "X::Syntax::Term::MissingInitializer"> ]
| <variable_declarator>
| '(' ~ ')' <signature> [ <.ws> <trait>+ ]? [ <.ws> <initializer> ]?
| <routine_declarator>
]
}
Expand Down
29 changes: 27 additions & 2 deletions src/Raku/ast/signature.rakumod
Expand Up @@ -601,12 +601,17 @@ class RakuAST::ParameterTarget is RakuAST::Node {
}

# A binding of a parameter into a lexical variable (with sigil).
class RakuAST::ParameterTarget::Var is RakuAST::ParameterTarget is RakuAST::Declaration {
class RakuAST::ParameterTarget::Var is RakuAST::ParameterTarget is RakuAST::Declaration
is RakuAST::Meta is RakuAST::ContainerCreator {
has str $.name;
has RakuAST::Type $.type;
has Mu $!of;

method new(str $name!) {
my $obj := nqp::create(self);
nqp::bindattr_s($obj, RakuAST::ParameterTarget::Var, '$!name', $name);
nqp::bindattr($obj, RakuAST::ParameterTarget::Var, '$!type', Mu);
nqp::bindattr($obj, RakuAST::ParameterTarget::Var, '$!of', Mu);
$obj
}

Expand All @@ -629,8 +634,28 @@ class RakuAST::ParameterTarget::Var is RakuAST::ParameterTarget is RakuAST::Decl
nqp::substr($!name, 0, 1)
}

method twigil() {
''
}

method set-container-type(Mu $type, Mu $of) {
nqp::bindattr(self, RakuAST::ParameterTarget::Var, '$!type', $type);
nqp::bindattr(self, RakuAST::ParameterTarget::Var, '$!of', $of);
}

method PRODUCE-META-OBJECT() {
self.IMPL-CONTAINER($!of)
}

method IMPL-QAST-DECL(RakuAST::IMPL::QASTContext $context) {
QAST::Var.new( :decl('var'), :scope('lexical'), :name($!name) )
if $!type {
my $container := self.meta-object;
$context.ensure-sc($container);
QAST::Var.new( :decl('contvar'), :scope('lexical'), :name($!name), :value($container) )
}
else {
QAST::Var.new( :decl('var'), :scope('lexical'), :name($!name) )
}
}

method IMPL-BIND-QAST(RakuAST::IMPL::QASTContext $context, Mu $source-qast) {
Expand Down
211 changes: 168 additions & 43 deletions src/Raku/ast/variable-declaration.rakumod
Expand Up @@ -44,9 +44,53 @@ class RakuAST::Initializer::Bind is RakuAST::Initializer {
}
}

class RakuAST::ContainerCreator {
method IMPL-CONTAINER(Mu $of) {
# If it's a natively typed scalar, no container.
my str $sigil := self.sigil;
my int $is-native := nqp::objprimspec($of);
if $sigil eq '$' && $is-native {
return Nil;
}

# Form container descriptor.
my $default := self.type ?? $of !! Any;
my int $dynamic := self.twigil eq '*' ?? 1 !! 0;
my $cont-desc := ContainerDescriptor.new(:$of, :$default, :$dynamic,
:name(self.lexical-name));

# Form the container.
my $container-base-type;
my $container-type;
if $sigil eq '@' {
$container-base-type := Array;
$container-type := self.type
?? Array.HOW.parameterize(Array, $of)
!! Array;
}
elsif $sigil eq '%' {
$container-base-type := Hash;
$container-type := self.type
?? Hash.HOW.parameterize(Hash, $of)
!! Hash;
}
else {
$container-base-type := Scalar;
$container-type := Scalar;
}
my $container := nqp::create($container-type);
nqp::bindattr($container, $container-base-type, '$!descriptor', $cont-desc);
unless $sigil eq '@' || $sigil eq '%' {
nqp::bindattr($container, $container-base-type, '$!value', $default);
}

$container
}
}

# A basic variable declaration of the form `my SomeType $foo = 42` or `has Foo $x .= new`.
class RakuAST::VarDeclaration::Simple is RakuAST::Declaration is RakuAST::ImplicitLookups
is RakuAST::TraitTarget
is RakuAST::TraitTarget is RakuAST::ContainerCreator
is RakuAST::Meta is RakuAST::Attaching {
has RakuAST::Type $.type;
has str $.name;
Expand Down Expand Up @@ -202,48 +246,6 @@ class RakuAST::VarDeclaration::Simple is RakuAST::Declaration is RakuAST::Implic
}
}

method IMPL-CONTAINER(Mu $of) {
# If it's a natively typed scalar, no container.
my str $sigil := self.sigil;
my int $is-native := nqp::objprimspec($of);
if $sigil eq '$' && $is-native {
return Nil;
}

# Form container descriptor.
my $default := $!type ?? $of !! Any;
my int $dynamic := self.twigil eq '*' ?? 1 !! 0;
my $cont-desc := ContainerDescriptor.new(:$of, :$default, :$dynamic,
:name($!name));

# Form the container.
my $container-base-type;
my $container-type;
if $sigil eq '@' {
$container-base-type := Array;
$container-type := $!type
?? Array.HOW.parameterize(Array, $of)
!! Array;
}
elsif $sigil eq '%' {
$container-base-type := Hash;
$container-type := $!type
?? Hash.HOW.parameterize(Hash, $of)
!! Hash;
}
else {
$container-base-type := Scalar;
$container-type := Scalar;
}
my $container := nqp::create($container-type);
nqp::bindattr($container, $container-base-type, '$!descriptor', $cont-desc);
unless $sigil eq '@' || $sigil eq '%' {
nqp::bindattr($container, $container-base-type, '$!value', $default);
}

$container
}

method IMPL-QAST-DECL(RakuAST::IMPL::QASTContext $context) {
my @lookups := self.IMPL-UNWRAP-LIST(self.get-implicit-lookups());
my str $scope := self.scope;
Expand Down Expand Up @@ -439,6 +441,129 @@ class RakuAST::VarDeclaration::Simple is RakuAST::Declaration is RakuAST::Implic
method needs-sink-call() { False }
}

class RakuAST::VarDeclaration::Signature is RakuAST::Declaration is RakuAST::ImplicitLookups
is RakuAST::TraitTarget is RakuAST::CheckTime
is RakuAST::Attaching {
has RakuAST::Signature $.signature;
has RakuAST::Type $.type;
has RakuAST::Initializer $.initializer;
has RakuAST::Package $!attribute-package;
has Mu $!package;

method new(RakuAST::Signature :$signature!, RakuAST::Type :$type, RakuAST::Initializer :$initializer,
str :$scope) {
my $obj := nqp::create(self);
nqp::bindattr($obj, RakuAST::VarDeclaration::Signature, '$!signature', $signature);
nqp::bindattr_s($obj, RakuAST::Declaration, '$!scope', $scope);
nqp::bindattr($obj, RakuAST::VarDeclaration::Signature, '$!type', $type // RakuAST::Type);
nqp::bindattr($obj, RakuAST::VarDeclaration::Signature, '$!initializer',
$initializer // RakuAST::Initializer);
$obj
}

method visit-children(Code $visitor) {
$visitor($!signature);
my $type := $!type;
$visitor($type) if nqp::isconcrete($type);
my $initializer := $!initializer;
$visitor($initializer) if nqp::isconcrete($initializer);
}

method default-scope() {
'my'
}

method allowed-scopes() {
self.IMPL-WRAP-LIST(['my', 'state', 'our', 'has', 'HAS'])
}

method is-simple-lexical-declaration() {
# The ParameterTargets in our signature will take care of declarations
False
}

method is-lexical() {
# Overridden here because our-scoped variables are really lexical aliases.
my str $scope := self.scope;
$scope eq 'my' || $scope eq 'state' || $scope eq 'our'
}

method attach(RakuAST::Resolver $resolver) {
my str $scope := self.scope;
if $scope eq 'has' || $scope eq 'HAS' {
my $attribute-package := $resolver.find-attach-target('package');
if $attribute-package {
nqp::bindattr(self, RakuAST::VarDeclaration::Signature, '$!attribute-package',
$attribute-package);
$attribute-package.ATTACH-ATTRIBUTE(self);
}
else {
# TODO check-time error
}
}
elsif $scope eq 'our' {
my $package := $resolver.current-package;
# There is always a package, even if it's just GLOBALish
nqp::bindattr(self, RakuAST::VarDeclaration::Signature, '$!package',
$package);
}
}

method PRODUCE-IMPLICIT-LOOKUPS() {
my @lookups;
# If it's our-scoped, we need the package to bind it from.
my str $scope := self.scope;
if $scope eq 'our' {
@lookups.push(RakuAST::Var::Compiler::Lookup.new('$?PACKAGE'));
}
# If we have a type, we need to resolve that.
elsif $!type {
@lookups.push($!type);
}
# If we're has/HAS scope, we need Nil to evaluate to.
if $scope eq 'has' || $scope eq 'HAS' {
@lookups.push(RakuAST::Type::Setting.new(RakuAST::Name.from-identifier('Nil')));
}
self.IMPL-WRAP-LIST(@lookups)
}

method PERFORM-CHECK(RakuAST::Resolver $resolver) {
# tell the parameter targets to create containers
my @params := self.IMPL-UNWRAP-LIST($!signature.parameters);

my @lookups := self.IMPL-UNWRAP-LIST(self.get-implicit-lookups());
my $of := $!type ?? @lookups[0].resolution.compile-time-value !! Mu;
my $type := $!type // RakuAST::Type::Setting.new('Mu');

for @params {
$_.target.set-container-type($type, $of);
}
}

method IMPL-TO-QAST(RakuAST::IMPL::QASTContext $context) {
if nqp::isconcrete($!initializer) {
if nqp::istype($!initializer, RakuAST::Initializer::Assign) {
my $list := QAST::Op.new( :op('call'), :name('&infix:<,>') );
my @params := self.IMPL-UNWRAP-LIST($!signature.parameters);

for @params {
$list.push: $_.target.IMPL-LOOKUP-QAST($context);
}
my $init-qast := $!initializer.IMPL-TO-QAST($context);
$list := QAST::Op.new( :op('p6store'), $list, $init-qast);
return $list;
}
else {
nqp::die('Not yet supported signature initializer: ' ~ $!initializer.HOW.name($!initializer));
}
}

QAST::Op.new(:op<null>);
}

method needs-sink-call() { False }
}

# An anonymous variable declaration, such as `my $ = 42`
class RakuAST::VarDeclaration::Anonymous is RakuAST::VarDeclaration::Simple {
has str $.sigil;
Expand Down

0 comments on commit a2c6bcb

Please sign in to comment.