The support for objects is an example of this. There's no problem with defining methods, or defining a class name. There's no problem with creating a new instance of the class. But that's where it stops being easy. Because the syntax for extending another class is missing.
One thing I'd like to do is subclass the PAST::Node class(es) in my own code, so I can use method invocations to call functions in a different namespace. This would change code like:
close::Compiler::Type::merge_specifiers($node1, $node2)into something like:
$node1.merge($node2);which would make my fingers happy, if nothing else.
Automatically generated, but wrong
One problem is that when I declare a class in NQP, like:
class close::Compiler::Type;NQP emits an initload block that creates a new class. The problem is that it creates the class with no parent. Whoops. So the first thing to do is to change from using a 'class' keyword to using 'module.'
Now I won't have any class definition at all, which is actually good. All I need now is to make my own initload that creates the class.
Make your own initload sub
The trick to rolling your own initload sub is not to do it -- you can't get there from here in NQP. What you can do, though, is take advantage of the fact that any code that is at package scope in your NQP source code is put into the initload sub for the class or module you are defining.
So, since package scope is an initload sub, my solution is to define a sub and call it from package scope:
_onload();Creating a class, the right way
sub _onload() {
say("Hello, from _onload");
}
Now that we know how to get a class created, it's time to dig into the PCT source code to see how it creates the classes. I looked in $parrot/compilers/pct/src/PAST/Node.pir, and found this:
p6meta = new 'P6metaclass'Well, that looks pretty simple! Let's convert that to NQP:
base = p6meta.'new_class'('PAST::Node', 'parent'=>'PCT::Node')
p6meta.'new_class'('PAST::Op', 'parent'=>base)
module PAST::Subclass {
_onload();
sub _onload() {Note that I chose to inherit from PAST::Node, rather than PCT::Node. Once you know the trick, you can subclass just about anything.
my $meta := Q:PIR { %r = new 'P6metaclass' };
$meta.new_class('PAST::Subclass', :parent('PAST::Node'));
}
}
The init problem
The next problem is that the P6metaclass system generates a really dumb 'new' method. So the easiest thing is to replace it. But if I replace it, how will I initialize the superclass data?
This is where you have to investigate on your own. In this case, I chose to inherit the PAST::Node version of 'new,' because it calls self.init(...), which I could override.
So I have an init method, but I need to call the PAST::Node::init method as well. That wouldn't be a problem, except that (1) there is no PAST::Node init method, it is inherited from PCT::Node; and (2) the PCT::Node init method wants its parameters flattened. It's more PIR code to the rescue, because NQP doesn't support flattening args:
sub init(*@children, *%attributes) {And now I've got a subclass. Just to bundle it into one big copy/pasteable bunch, here you go:
# do my own stuff ...
# ... then call
Q:PIR {
.local pmc children, attributes
children = find_lex '@children'
attributes = find_lex '%attributes'
$P0 = get_hll_global [ 'PCT' ; 'Node' ], 'init'
self.$P0(children :flat, attributes :named :flat)
};
return self;
}
module PAST::Subclass {
_onload();
sub _onload() {
my $meta := Q:PIR { %r = new 'P6metaclass' };
$meta.new_class('PAST::Subclass', :parent('PAST::Node'));
}
sub init(*@children, *%attributes) {
# do my own stuff ...
# ... then call
Q:PIR {
.local pmc children, attributes
children = find_lex '@children'
attributes = find_lex '%attributes'
$P0 = get_hll_global [ 'PCT' ; 'Node' ], 'init'
self.$P0(children :flat, attributes :named :flat)
};
return self;
}
}
Was looking up information on nodes and nqp as was having trouble understanding them! your webpage is a good resource and is very helpful
ReplyDelete