Skip to content

Commit

Permalink
RakuAST support for various regex assertion forms
Browse files Browse the repository at this point in the history
  • Loading branch information
jnthn committed Jul 8, 2020
1 parent 89a19d7 commit cef320b
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/Raku/Actions.nqp
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,13 @@ class Raku::RegexActions is HLL::Actions {
$res := nqp::atkey($res.WHO, $t3) unless nqp::isnull($res);
nqp::ifnull($res, nqp::die("No such node RakuAST::{$t1}::{$t2}::{$t3}"))
}
multi method r($t1, $t2, $t3, $t4) {
my $res := nqp::atkey($ast_root, $t1);
$res := nqp::atkey($res.WHO, $t2) unless nqp::isnull($res);
$res := nqp::atkey($res.WHO, $t3) unless nqp::isnull($res);
$res := nqp::atkey($res.WHO, $t4) unless nqp::isnull($res);
nqp::ifnull($res, nqp::die("No such node RakuAST::{$t1}::{$t2}::{$t3}::{$t4}"))
}

method nibbler($/) {
make $<termseq>.ast;
Expand Down Expand Up @@ -957,4 +964,58 @@ class Raku::RegexActions is HLL::Actions {
my constant NAME := nqp::hash('d', 'Digit', 'n', 'Newline', 's', 'Space', 'w', 'Word');
make self.r('Regex', 'CharClass', NAME{nqp::lc(~$<sym>)}).new(negated => $<sym> le 'Z');
}

method assertion:sym<?>($/) {
if $<assertion> {
my $assertion := $<assertion>.ast;
make self.r('Regex', 'Assertion', 'Lookahead').new(:$assertion);
}
else {
make self.r('Regex', 'Assertion', 'Pass').new();
}
}

method assertion:sym<!>($/) {
if $<assertion> {
my $assertion := $<assertion>.ast;
make self.r('Regex', 'Assertion', 'Lookahead').new(:$assertion, :negated);
}
else {
make self.r('Regex', 'Assertion', 'Fail').new();
}
}

method assertion:sym<method>($/) {
my $ast := $<assertion>.ast;
if nqp::can($ast, 'set-capturing') {
$ast.set-capturing(0);
}
make $ast;
}

method assertion:sym<name>($/) {
my $name := ~$<longname>;
my $qast;
if $<assertion> {
my str $name := ~$<longname>;
my $assertion := $<assertion>.ast;
make self.r('Regex', 'Assertion', 'Alias').new(:$name, :$assertion);
}
else {
my $name := $<longname>.ast;
if $<arglist> {
my $args := $<arglist>.ast;
make self.r('Regex', 'Assertion', 'Named', 'Args').new(
:$name, :capturing, :$args);
}
elsif $<nibbler> {
my $regex-arg := $<nibbler>.ast;
make self.r('Regex', 'Assertion', 'Named', 'RegexArg').new(
:$name, :capturing, :$regex-arg);
}
else {
make self.r('Regex', 'Assertion', 'Named').new(:$name, :capturing);
}
}
}
}
17 changes: 17 additions & 0 deletions src/Raku/Grammar.nqp
Original file line number Diff line number Diff line change
Expand Up @@ -1777,4 +1777,21 @@ grammar Raku::RegexGrammar is QRegex::P6Regex::Grammar does Raku::Common {
token normspace { <?before \s | '#'> <.LANG('MAIN', 'ws')> }

token rxstopper { <stopper> }

token assertion:sym<name> {
<longname=.LANG('MAIN','longname')>
[
| <?[>]>
| '=' <assertion>
| ':' <arglist>
| '(' <arglist> ')'
| <.normspace> <nibbler>
]?
}

token arglist {
:my $*IN_REGEX_ASSERTION := 1;
<!RESTRICTED>
<arglist=.LANG('MAIN','arglist')>
}
}
159 changes: 159 additions & 0 deletions src/Raku/ast/regex.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,165 @@ class RakuAST::Regex::CharClass::Word is RakuAST::Regex::CharClass::Negatable {
}
}

# The base of all regex assertions (things of the form `<...>`, such as subrule
# calls, lookaheads, and user-defined character classes).
class RakuAST::Regex::Assertion is RakuAST::Regex::Atom {
}

# An assertion that always passes.
class RakuAST::Regex::Assertion::Pass is RakuAST::Regex::Assertion {
method new() {
nqp::create(self)
}

method IMPL-REGEX-QAST(RakuAST::IMPL::QASTContext $context, %mods) {
QAST::Regex.new( :rxtype<anchor>, :subtype<pass> )
}
}

# An assertion that always fails.
class RakuAST::Regex::Assertion::Fail is RakuAST::Regex::Assertion {
method new() {
nqp::create(self)
}

method IMPL-REGEX-QAST(RakuAST::IMPL::QASTContext $context, %mods) {
QAST::Regex.new( :rxtype<anchor>, :subtype<fail> )
}
}

# A named assertion, which may or may not capture. Models `<foo>` and
# `<.foo>`, and also `<foo::bar>`. Forms with arguments or taking a regex
# argument are modeled as subclasses of this.
class RakuAST::Regex::Assertion::Named is RakuAST::Regex::Assertion {
has RakuAST::Name $.name;
has Bool $.capturing;

method new(RakuAST::Name :$name!, Bool :$capturing) {
my $obj := nqp::create(self);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!name', $name);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!capturing',
$capturing ?? True !! False);
$obj
}

method set-capturing(Bool $capturing) {
nqp::bindattr(self, RakuAST::Regex::Assertion::Named, '$!capturing',
$capturing ?? True !! False);
Nil
}

method IMPL-REGEX-QAST(RakuAST::IMPL::QASTContext $context, %mods) {
my $longname := $!name;
if $longname.is-identifier {
my $name := $longname.canonicalize;
if $name eq 'sym' {
nqp::die('special <sym> name not yet compiled');
}
else {
my $qast := QAST::Regex.new: :rxtype<subrule>,
QAST::NodeList.new(QAST::SVal.new( :value($name) ));
if $!capturing {
$qast.subtype('capture');
$qast.name($name);
}
$qast
}
}
else {
nqp::die('non-identifier rule calls not yet compiled');
}
}
}

# A named rule called with args.
class RakuAST::Regex::Assertion::Named::Args is RakuAST::Regex::Assertion::Named {
has RakuAST::ArgList $.args;

method new(RakuAST::Name :$name!, Bool :$capturing, Raku::ArgList :$args!) {
my $obj := nqp::create(self);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!name', $name);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!capturing',
$capturing ?? True !! False);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named::Args, '$!args', $args);
$obj
}
}

# A named rule called with a regex argument.
class RakuAST::Regex::Assertion::Named::RegexArg is RakuAST::Regex::Assertion::Named {
has RakuAST::Regex $.regex-arg;

method new(RakuAST::Name :$name!, Bool :$capturing, Raku::Regex :$regex-arg!) {
my $obj := nqp::create(self);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!name', $name);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named, '$!capturing',
$capturing ?? True !! False);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Named::RegexArg,
'$!regex-arg', $regex-arg);
$obj
}
}

# An alias assertion (where another assertion is given an extra name - or, in
# the case it's anonymous, perhaps just a name).
class RakuAST::Regex::Assertion::Alias is RakuAST::Regex::Assertion {
has str $.name;
has RakuAST::Regex::Assertion $.assertion;

method new(str :$name!, RakuAST::Regex::Assertion :$assertion!) {
my $obj := nqp::create(self);
nqp::bindattr_s($obj, RakuAST::Regex::Assertion::Alias, '$!name', $name);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Alias, '$!assertion', $assertion);
$obj
}

method IMPL-REGEX-QAST(RakuAST::IMPL::QASTContext $context, %mods) {
my $qast := $!assertion.IMPL-REGEX-QAST($context, %mods);
if $qast.rxtype eq 'subrule' {
self.IMPL-SUBRULE-ALIAS($qast, $!name);
}
else {
QAST::Regex.new( $qast, :name($!name), :rxtype<subcapture> );
}
}

method IMPL-SUBRULE-ALIAS(Mu $qast, str $name) {
if $qast.name gt '' {
$qast.name($name ~ '=' ~ $qast.name);
}
else {
$qast.name($name);
}
$qast.subtype('capture');
$qast
}
}

# A lookahead assertion (where another assertion is evaluated as a
# zerowidth lookahead, either positive or negative).
class RakuAST::Regex::Assertion::Lookahead is RakuAST::Regex::Assertion {
has Bool $.negated;
has RakuAST::Regex::Assertion $.assertion;

method new(Bool :$negated, RakuAST::Regex::Assertion :$assertion!) {
my $obj := nqp::create(self);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Lookahead, '$!negated',
$negated ?? True !! False);
nqp::bindattr($obj, RakuAST::Regex::Assertion::Lookahead, '$!assertion', $assertion);
$obj
}

method IMPL-REGEX-QAST(RakuAST::IMPL::QASTContext $context, %mods) {
my $qast := $!assertion.IMPL-REGEX-QAST($context, %mods);
$qast.subtype('zerowidth');
if $!negated {
$qast.negate(!$qast.negate);
}
$qast
}
}

# A quantified atom in a regex - that is, an atom with a quantifier and
# optional separator.
class RakuAST::Regex::QuantifiedAtom is RakuAST::Regex::Term {
Expand Down

0 comments on commit cef320b

Please sign in to comment.