Sidef/lib/Sidef/Parser.pm
package Sidef::Parser {
use utf8;
use 5.016;
use Sidef::Types::Bool::Bool;
use List::Util qw(first);
use Scalar::Util qw(refaddr);
# our $REGMARK;
sub new {
my (undef, %opts) = @_;
my %options = (
line => 1,
inc => [],
class => 'main', # a.k.a. namespace
vars => {'main' => []},
ref_vars_refs => {'main' => []},
EOT => [],
postfix_ops => { # postfix operators
'--' => 1,
'++' => 1,
'...' => 1,
'!' => 1,
'!!' => 1,
},
hyper_ops => {
# type => [takes args, method name]
map => [1, 'map_operator'],
pam => [1, 'pam_operator'],
zip => [1, 'zip_operator'],
wise => [1, 'wise_operator'],
scalar => [1, 'scalar_operator'],
rscalar => [1, 'rscalar_operator'],
cross => [1, 'cross_operator'],
unroll => [1, 'unroll_operator'],
reduce => [0, 'reduce_operator'],
lmap => [0, 'map_operator'],
},
static_obj_re => qr{\G
(?:
nil\b (?{ state $x = bless({}, 'Sidef::Types::Nil::Nil') })
| null\b (?{ state $x = Sidef::Types::Null::Null->new })
| true\b (?{ Sidef::Types::Bool::Bool::TRUE })
| false\b (?{ Sidef::Types::Bool::Bool::FALSE })
| next\b (?{ state $x = bless({}, 'Sidef::Types::Block::Next') })
| break\b (?{ state $x = bless({}, 'Sidef::Types::Block::Break') })
| Block\b (?{ state $x = bless({}, 'Sidef::DataTypes::Block::Block') })
| Backtick\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::Backtick') })
| ARGF\b (?{ state $x = bless({}, 'Sidef::Meta::Glob::ARGF') })
| STDIN\b (?{ state $x = bless({}, 'Sidef::Meta::Glob::STDIN') })
| STDOUT\b (?{ state $x = bless({}, 'Sidef::Meta::Glob::STDOUT') })
| STDERR\b (?{ state $x = bless({}, 'Sidef::Meta::Glob::STDERR') })
| Bool\b (?{ state $x = bless({}, 'Sidef::DataTypes::Bool::Bool') })
| FileHandle\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::FileHandle') })
| DirHandle\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::DirHandle') })
| SocketHandle\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::SocketHandle') })
| Dir\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::Dir') })
| File\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::File') })
| Arr(?:ay)?+\b (?{ state $x = bless({}, 'Sidef::DataTypes::Array::Array') })
| Pair\b (?{ state $x = bless({}, 'Sidef::DataTypes::Array::Pair') })
| Vec(?:tor)?\b (?{ state $x = bless({}, 'Sidef::DataTypes::Array::Vector') })
| Matrix\b (?{ state $x = bless({}, 'Sidef::DataTypes::Array::Matrix') })
| Hash\b (?{ state $x = bless({}, 'Sidef::DataTypes::Hash::Hash') })
| Set\b (?{ state $x = bless({}, 'Sidef::DataTypes::Set::Set') })
| Bag\b (?{ state $x = bless({}, 'Sidef::DataTypes::Set::Bag') })
| Str(?:ing)?+\b (?{ state $x = bless({}, 'Sidef::DataTypes::String::String') })
| Num(?:ber)?+\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Number') })
| Mod\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Mod') })
| Gauss\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Gauss') })
| Quadratic\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Quadratic') })
| Quaternion\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Quaternion') })
| Poly(?:nomial)?\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Polynomial') })
| Poly(?:nomial)?Mod\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::PolynomialMod') })
| Frac(?:tion)?\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Fraction') })
| Inf\b (?{ state $x = Sidef::Types::Number::Number->inf })
| NaN\b (?{ state $x = Sidef::Types::Number::Number->nan })
| Infi\b (?{ state $x = Sidef::Types::Number::Complex->new(0, Sidef::Types::Number::Number->inf) })
| NaNi\b (?{ state $x = Sidef::Types::Number::Complex->new(0, Sidef::Types::Number::Number->nan) })
| RangeNum(?:ber)?+\b (?{ state $x = bless({}, 'Sidef::DataTypes::Range::RangeNumber') })
| RangeStr(?:ing)?+\b (?{ state $x = bless({}, 'Sidef::DataTypes::Range::RangeString') })
| Range\b (?{ state $x = bless({}, 'Sidef::DataTypes::Range::Range') })
| Socket\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::Socket') })
| Pipe\b (?{ state $x = bless({}, 'Sidef::DataTypes::Glob::Pipe') })
| Ref\b (?{ state $x = bless({}, 'Sidef::Variable::Ref') })
| NamedParam\b (?{ state $x = bless({}, 'Sidef::DataTypes::Variable::NamedParam') })
| Lazy\b (?{ state $x = bless({}, 'Sidef::DataTypes::Object::Lazy') })
| LazyMethod\b (?{ state $x = bless({}, 'Sidef::DataTypes::Object::LazyMethod') })
| Enumerator\b (?{ state $x = bless({}, 'Sidef::DataTypes::Object::Enumerator') })
| Complex\b (?{ state $x = bless({}, 'Sidef::DataTypes::Number::Complex') })
| Regexp?\b (?{ state $x = bless({}, 'Sidef::DataTypes::Regex::Regex') })
| Object\b (?{ state $x = bless({}, 'Sidef::DataTypes::Object::Object') })
| Sidef\b (?{ state $x = bless({}, 'Sidef::DataTypes::Sidef::Sidef') })
| Sig\b (?{ state $x = bless({}, 'Sidef::Sys::Sig') })
| Sys\b (?{ state $x = bless({}, 'Sidef::Sys::Sys') })
| Perl\b (?{ state $x = bless({}, 'Sidef::DataTypes::Perl::Perl') })
| Math\b (?{ state $x = bless({}, 'Sidef::Math::Math') })
| Time\b (?{ state $x = Sidef::Time::Time->new })
| Date\b (?{ state $x = Sidef::Time::Date->new })
| \$\. (?{ state $x = bless({name => '$.'}, 'Sidef::Variable::Magic') })
| \$\? (?{ state $x = bless({name => '$?'}, 'Sidef::Variable::Magic') })
| \$\$ (?{ state $x = bless({name => '$$'}, 'Sidef::Variable::Magic') })
| \$\^T\b (?{ state $x = bless({name => '$^T'}, 'Sidef::Variable::Magic') })
| \$\| (?{ state $x = bless({name => '$|'}, 'Sidef::Variable::Magic') })
| \$! (?{ state $x = bless({name => '$!'}, 'Sidef::Variable::Magic') })
| \$" (?{ state $x = bless({name => '$"'}, 'Sidef::Variable::Magic') })
| \$\\ (?{ state $x = bless({name => '$\\'}, 'Sidef::Variable::Magic') })
| \$@ (?{ state $x = bless({name => '$@'}, 'Sidef::Variable::Magic') })
| \$% (?{ state $x = bless({name => '$%'}, 'Sidef::Variable::Magic') })
| \$~ (?{ state $x = bless({name => '$~'}, 'Sidef::Variable::Magic') })
| \$/ (?{ state $x = bless({name => '$/'}, 'Sidef::Variable::Magic') })
| \$& (?{ state $x = bless({name => '$&'}, 'Sidef::Variable::Magic') })
| \$' (?{ state $x = bless({name => '$\''}, 'Sidef::Variable::Magic') })
| \$` (?{ state $x = bless({name => '$`'}, 'Sidef::Variable::Magic') })
| \$: (?{ state $x = bless({name => '$:'}, 'Sidef::Variable::Magic') })
| \$\] (?{ state $x = bless({name => '$]'}, 'Sidef::Variable::Magic') })
| \$\[ (?{ state $x = bless({name => '$['}, 'Sidef::Variable::Magic') })
| \$; (?{ state $x = bless({name => '$;'}, 'Sidef::Variable::Magic') })
| \$, (?{ state $x = bless({name => '$,'}, 'Sidef::Variable::Magic') })
| \$\^O\b (?{ state $x = bless({name => '$^O'}, 'Sidef::Variable::Magic') })
| \$\^PERL\b (?{ state $x = bless({name => '$^X', dump => '$^PERL'}, 'Sidef::Variable::Magic') })
| (?:\$0|\$\^SIDEF)\b (?{ state $x = bless({name => '$0', dump => '$^SIDEF'}, 'Sidef::Variable::Magic') })
| \$\) (?{ state $x = bless({name => '$)'}, 'Sidef::Variable::Magic') })
| \$\( (?{ state $x = bless({name => '$('}, 'Sidef::Variable::Magic') })
| \$< (?{ state $x = bless({name => '$<'}, 'Sidef::Variable::Magic') })
| \$> (?{ state $x = bless({name => '$>'}, 'Sidef::Variable::Magic') })
| ∞ (?{ state $x = Sidef::Types::Number::Number->inf })
) (?!::)
}x,
prefix_obj_re => qr{\G
(?:
if\b (?{ bless({}, 'Sidef::Types::Block::If') })
| with\b (?{ bless({}, 'Sidef::Types::Block::With') })
| while\b (?{ bless({}, 'Sidef::Types::Block::While') })
| foreach\b (?{ bless({}, 'Sidef::Types::Block::ForEach') })
| for\b (?{ bless({}, 'Sidef::Types::Block::For') })
| return\b (?{ state $x = bless({}, 'Sidef::Types::Block::Return') })
#| next\b (?{ bless({}, 'Sidef::Types::Block::Next') })
#| break\b (?{ bless({}, 'Sidef::Types::Block::Break') })
| read\b (?{ state $x = Sidef::Sys::Sys->new })
| goto\b (?{ state $x = bless({}, 'Sidef::Perl::Builtin') })
| (?:[*\\]|\+\+|--) (?{ state $x = bless({}, 'Sidef::Variable::Ref') })
| (?:>>?|\@\|?|[√+~!\-\^]|
(?:
say
| print
| defined
)\b) (?{ state $x = bless({}, 'Sidef::Operator::Unary') })
| : (?{ state $x = bless({}, 'Sidef::Meta::PrefixColon') })
)
}x,
quote_operators_re => qr{\G
(?:
# String
(?: ['‘‚’] | %q\b. ) (?{ [qw(0 new Sidef::Types::String::String)] })
|(?: ["“„”] | %(?:Q\b. | (?!\w). )) (?{ [qw(1 new Sidef::Types::String::String)] })
# File
| %f\b. (?{ [qw(0 new Sidef::Types::Glob::File)] })
| %F\b. (?{ [qw(1 new Sidef::Types::Glob::File)] })
# Dir
| %d\b. (?{ [qw(0 new Sidef::Types::Glob::Dir)] })
| %D\b. (?{ [qw(1 new Sidef::Types::Glob::Dir)] })
# Pipe
| %p\b. (?{ [qw(0 pipe Sidef::Types::Glob::Pipe)] })
| %P\b. (?{ [qw(1 pipe Sidef::Types::Glob::Pipe)] })
# Backtick
| %x\b. (?{ [qw(0 new Sidef::Types::Glob::Backtick)] })
| (?: %X\b. | ` ) (?{ [qw(1 new Sidef::Types::Glob::Backtick)] })
# Bytes
| %b\b. (?{ [qw(0 bytes Sidef::Types::Array::Array)] })
| %B\b. (?{ [qw(1 bytes Sidef::Types::Array::Array)] })
# Chars
| %c\b. (?{ [qw(0 chars Sidef::Types::Array::Array)] })
| %C\b. (?{ [qw(1 chars Sidef::Types::Array::Array)] })
# Graphemes
| %g\b. (?{ [qw(0 graphemes Sidef::Types::Array::Array)] })
| %G\b. (?{ [qw(1 graphemes Sidef::Types::Array::Array)] })
# Symbols
| %[Os]\b. (?{ [qw(0 __NEW__ Sidef::Module::OO)] })
| %S\b. (?{ [qw(0 __NEW__ Sidef::Module::Func)] })
# Arbitrary Perl code
| %perl\b. (?{ [qw(0 new Sidef::Types::Perl::Perl)] })
| %Perl\b. (?{ [qw(1 new Sidef::Types::Perl::Perl)] })
)
}xs,
built_in_classes => {
map { $_ => 1 }
qw(
File
FileHandle
Dir
DirHandle
Arr Array
Pair
Vec Vector
Matrix
Enumerator
Hash
Set
Bag
Str String
Num Number
Poly Polynomial
PolyMod PolynomialMod
Frac Fraction
Mod
Gauss
Quadratic
Quaternion
Range
RangeStr RangeString
RangeNum RangeNumber
Complex
Math
Pipe
Ref
Socket
SocketHandle
Bool
Sys
Sig
Regex Regexp
Time
Date
Perl
Sidef
Object
Parser
Block
Backtick
Lazy
LazyMethod
NamedParam
true false
nil null
)
},
keywords => {
map { $_ => 1 }
qw(
next
break
return
for foreach
if elsif else
with orwith
while
given
with
continue
import
include
eval
read
die
warn
assert
assert_eq
assert_ne
local
global
var
del
const
func
enum
class
static
define
struct
subset
module
DATA
ARGV
ARGF
ENV
STDIN
STDOUT
STDERR
__FILE__
__LINE__
__END__
__DATA__
__TIME__
__DATE__
__NAMESPACE__
__COMPILED__
__OPTIMIZED__
)
},
match_flags_re => qr{[msixpogcaludn]+},
var_name_re => qr/[^\W\d]\w*+(?>::[^\W\d]\w*)*/,
method_name_re => qr/[^\W\d]\w*+!?/,
var_init_sep_re => qr/\G\h*(?:=>|[=:])\h*/,
operators_re => do {
local $" = q{|};
# Longest prefix first
my @operators = map { quotemeta } qw(
||= ||
&&= &&
^.. ..^
%% ≅
~~ !~
<~>
<=> =~=
<<= >>=
<< >>
|>> |> |X> |Z>
|= |
&= &
== =~
:= =
<= >= < >
++ --
+= +
-= -
//= //
/= / ÷= ÷
**= **
%= %
^= ^
*= *
...
!= ..
\\\\= \\\\
!! !
: : ⫶
« » ~
);
qr{
(?(DEFINE)
(?<ops>
@operators
| \p{Block: Mathematical_Operators}
| \p{Block: Supplemental_Mathematical_Operators}
)
)
»(?<unroll>[^\W\d]\w*+|(?&ops))« # unroll operator (e.g.: »add« or »+«)
| >>(?<unroll>[^\W\d]\w*+|(?&ops))<< # unroll operator (e.g.: >>add<< or >>+<<)
| ~X(?<cross>[^\W\d]\w*+|(?&ops)|) # cross operator (e.g.: ~X or ~X+)
| ~Z(?<zip>[^\W\d]\w*+|(?&ops)|) # zip operator (e.g.: ~Z or ~Z+)
| ~W(?<wise>[^\W\d]\w*+|(?&ops)|) # wise operator (e.g.: ~W or ~W+)
| ~S(?<scalar>[^\W\d]\w*+|(?&ops)|) # scalar operator (e.g.: ~S or ~S+)
| ~RS(?<rscalar>[^\W\d]\w*+|(?&ops)|) # reverse scalar operator (e.g.: ~RS or ~RS/)
| »(?<map>[^\W\d]\w*+|(?&ops))» # mapping operator (e.g.: »add» or »+»)
| >>(?<map>[^\W\d]\w*+|(?&ops))>> # mapping operator (e.g.: >>add>> or >>+>>)
| «(?<pam>[^\W\d]\w*+|(?&ops))« # reverse mapping operator (e.g.: «add« or «+«)
| <<(?<pam>[^\W\d]\w*+|(?&ops))<< # reverse mapping operator (e.g.: <<add<< or <<+<<)
| »(?<lmap>[^\W\d]\w*+|(?&ops))\(\)» # mapping operator (e.g.: »add()» or »+()»)
| >>(?<lmap>[^\W\d]\w*+|(?&ops))\(\)>> # mapping operator (e.g.: >>add()>> or >>+()>>)
| <<(?<reduce>[^\W\d]\w*+|(?&ops))>> # reduce operator (e.g.: <<add>> or <<+>>)
| «(?<reduce>[^\W\d]\w*+|(?&ops))» # reduce operator (e.g.: «add» or «+»)
| `(?<op>[^\W\d]\w*+!?)` # method-like operator (e.g.: `add` or `add!`)
| (?<op>(?&ops)) # primitive operator (e.g.: +, -, *, /)
}x;
},
# Reference: https://en.wikipedia.org/wiki/International_variation_in_quotation_marks
delim_pairs => {
qw~
( ) [ ] { } < >
« » » « ‹ › › ‹
„ ” “ ” ‘ ’ ‚ ’
〈 〉 ﴾ ﴿ 〈 〉 《 》
「 」 『 』 【 】 〔 〕
〖 〗 〘 〙 〚 〛 ⸨ ⸩
⌈ ⌉ ⌊ ⌋ 〈 〉 ❨ ❩
❪ ❫ ❬ ❭ ❮ ❯ ❰ ❱
❲ ❳ ❴ ❵ ⟅ ⟆ ⟦ ⟧
⟨ ⟩ ⟪ ⟫ ⟬ ⟭ ⟮ ⟯
⦃ ⦄ ⦅ ⦆ ⦇ ⦈ ⦉ ⦊
⦋ ⦌ ⦍ ⦎ ⦏ ⦐ ⦑ ⦒
⦗ ⦘ ⧘ ⧙ ⧚ ⧛ ⧼ ⧽
~
},
%opts,
);
$options{ref_vars} = $options{vars};
$options{file_name} //= '-';
$options{script_name} //= '-';
bless \%options, __PACKAGE__;
}
sub fatal_error {
my ($self, %opt) = @_;
my $start = rindex($opt{code}, "\n", $opt{pos}) + 1;
my $point = $opt{pos} - $start;
my $line = $opt{line} // $self->{line};
my $column = $point;
my $error_line = (split(/\R/, substr($opt{code}, $start, $point + 80)))[0];
if (length($error_line) > 80 && $point > 60) {
my $from = $point - 40;
my $rem = $point + 40 - length($error_line);
$from -= $rem;
$point = 40 + $rem;
$error_line = substr($error_line, $from, 80);
}
my @lines = (
"Your code forgot how to 'human.' Please give it some structure!",
"Were you trying to summon Cthulhu, or is this just a typo?",
"This line of code is in denial. It doesn't want to cooperate.",
"Parser has encountered a disturbance in the Force. It appears your code has gone to the dark side.",
"Code drunk. Fix before it starts ordering random APIs online.",
"Did you mean to do that? Because your code just broke the Matrix.",
"Your syntax seems to have taken a coffee break.",
"Your code is trying to summon Skynet. Abort while you can!",
"This isn’t a Choose Your Own Adventure. Stick to the script!",
"Well, that’s not going to work. Try again, friend.",
"If this is what you meant, we need to have a talk.",
"Uh-oh! Looks like your code slipped on a banana peel.",
"Your code is having an existential crisis. It doesn’t know who it is anymore.",
"Something’s wrong… but hey, at least you tried!",
"The code didn’t like that. In fact, it’s offended.",
"You’re one typo away from greatness. Unfortunately, this isn’t it.",
"Well, that’s awkward. Let’s pretend that didn’t happen.",
"Your code needs more coffee. Or maybe you do.",
"I have no idea what I just read.",
"Oops! The parser is confused and needs a moment to reboot its brain.",
"The code is speaking in riddles. Try again in plain text.",
"Parser ran out of breadcrumbs to follow your logic trail.",
"It’s like trying to read a book with missing pages.",
"The code went that way… and then it didn’t.",
"Parser gave up. It’s currently hiding under the desk.",
"It’s not you, it’s… okay, it’s definitely you.",
"Parser needs a map and a flashlight to navigate this code.",
"Parser has encountered a wild puzzle. It refuses to continue without a cheat sheet.",
"Your code just sent me on a wild goose chase. No geese found.",
"The parser tried to follow the logic, but it got lost in the spaghetti code.",
"Your code just opened a wormhole. Destination: unknown.",
"I’ve parsed Shakespeare, but this? This is next level.",
"The parser tripped over a missing semicolon and is now in the fetal position.",
"This code smells… like it’s on fire. Someone grab an extinguisher!",
"The parser and the code had a staring contest. The parser blinked first.",
"Parser has lost the plot. Please send a clearer script.",
"The code just threw a plot twist. Even the parser didn’t see that coming.",
"It tried to make sense of your code, then rage quit.",
"The code ventured into the unknown… and didn’t leave a map.",
"The parser tried to keep up, but your code is like a soap opera—too many plot twists.",
"Your code is speaking Parseltongue. I need an interpreter.",
"The parser has encountered an enigma wrapped in a mystery, coded in confusion.",
"The parser felt a great disturbance in the code, as if thousands of bugs cried out in terror.",
"The code just ghosted me. No clues, no closure, nothing.",
"Parser tangled in a logic loop. Send help. Or snacks.",
"The code is playing hide and seek. The parser isn’t winning.",
"The parser tried reasoning with the code, but it’s ignoring all logic.",
"Parser has encountered Schrödinger’s code. It’s both broken and not broken, but we won’t know until you fix it.",
);
my $error = sprintf("%s: %s\n\nFile : %s\nLine : %s : %s\nError: %s\n\n" . ("~" x 80) . "\n%s\n",
'Parsing error',
$lines[rand @lines],
$self->{file_name} // '-',
$line, $column, join(', ', grep { defined } $opt{error}, $opt{reason}), $error_line);
$error .= ' ' x ($point) . '^' . "\n" . ('~' x 80) . "\n";
if (exists($opt{var})) {
my ($name, $class) = $self->get_name_and_class($opt{var});
my %seen;
my @names;
foreach my $var (@{$self->{vars}{$class}}) {
next if ref $var eq 'ARRAY';
if (!$seen{$var->{name}}++) {
push @names, $var->{name};
}
}
foreach my $var (@{$self->{ref_vars_refs}{$class}}) {
next if ref $var eq 'ARRAY';
if (!$seen{$var->{name}}++) {
push @names, $var->{name};
}
}
if ($class eq 'main') {
$class = '';
}
else {
$class .= '::';
}
if (my @candidates = Sidef::best_matches($name, [grep { $_ ne $name } @names])) {
$error .= ("[?] Did you mean: " . join("\n" . (' ' x 18), map { $class . $_ } sort(@candidates)) . "\n");
}
}
die $error;
}
sub find_var {
my ($self, $var_name, $class) = @_;
foreach my $var (@{$self->{vars}{$class}}) {
next if ref $var eq 'ARRAY';
if ($var->{name} eq $var_name) {
return (wantarray ? ($var, 1) : $var);
}
}
foreach my $var (@{$self->{ref_vars_refs}{$class}}) {
next if ref $var eq 'ARRAY';
if ($var->{name} eq $var_name) {
return (wantarray ? ($var, 0) : $var);
}
}
return;
}
sub check_declarations {
my ($self, $hash_ref) = @_;
foreach my $class (grep { $_ eq 'main' } keys %{$hash_ref}) {
my $array_ref = $hash_ref->{$class};
foreach my $variable (@{$array_ref}) {
if (ref $variable eq 'ARRAY') {
$self->check_declarations({$class => $variable});
}
elsif ($self->{interactive} or $self->{eval_mode}) {
## Everything is OK in interactive mode
}
elsif ( $variable->{count} == 0
&& $variable->{type} ne 'class'
&& $variable->{type} ne 'func'
&& $variable->{type} ne 'method'
&& $variable->{type} ne 'global'
&& $variable->{name} ne 'self'
&& $variable->{name} ne ''
&& $variable->{type} ne 'del'
&& chr(ord $variable->{name}) ne '_') {
warn '[WARNING] '
. "$variable->{type} '$variable->{name}' has been declared, but not used again at "
. "$self->{file_name} line $variable->{line}\n";
}
}
}
}
sub get_name_and_class {
my ($self, $var_name) = @_;
$var_name // return ('', $self->{class});
my $rindex = rindex($var_name, '::');
$rindex != -1
? (substr($var_name, $rindex + 2), substr($var_name, 0, $rindex))
: ($var_name, $self->{class});
}
sub get_quoted_words {
my ($self, %opt) = @_;
my $string = $self->get_quoted_string(code => $opt{code}, no_count_line => 1);
$self->parse_whitespace(code => \$string);
my @words;
while ($string =~ /\G((?>[^\s\\]+|\\.)++)/gcs) {
push @words, $1 =~ s{\\#}{#}gr;
$self->parse_whitespace(code => \$string);
}
return \@words;
}
sub get_quoted_string {
my ($self, %opt) = @_;
local *_ = $opt{code};
/\G(?=\s)/ && $self->parse_whitespace(code => $opt{code});
my $delim;
if (/\G(?=(.))/) {
$delim = $1;
if ($delim eq '\\' && /\G\\(.*?)\\/gsc) {
return $1;
}
}
else {
$self->fatal_error(
error => qq{can't find the beginning of a string quote delimiter},
code => $_,
pos => pos($_),
);
}
my $orig_pos = pos($_);
my $beg_delim = quotemeta $delim;
my $pair_delim = exists($self->{delim_pairs}{$delim}) ? $self->{delim_pairs}{$delim} : ();
my $string = '';
if (defined $pair_delim) {
my $end_delim = quotemeta $pair_delim;
my $re_delim = $beg_delim . $end_delim;
# if (m{\G(?<main>$beg_delim((?>[^$re_delim\\]+|\\.|(?&main))*+)$end_delim)}sgc) {
# if (m{\G(?<main>$beg_delim((?>[^\\$re_delim]*+(?>\\.[^\\$re_delim]*|(?&main)){0,}){0,})(?:$end_delim(*ACCEPT:term))?)(*PRUNE)(*FAIL)}sgc) {
if (m{\G(?<main>$beg_delim((?>[^\\$re_delim]*+(?>\\.[^\\$re_delim]*|(?&main)){0,}){0,})$end_delim)}sgc) {
$string = $2 =~ s/\\([$re_delim])/$1/gr;
}
}
# elsif (m{\G$beg_delim([^\\$beg_delim]*+(?>\\.[^\\$beg_delim]*)*)}sgc) { # limited to 2^15-1 escapes
# elsif (m{\G$beg_delim((?>(?>[^$beg_delim\\]++|\\.){0,}+){0,}+)}sgc) {
# elsif (m{\G$beg_delim((?>[^\\$beg_delim]*+(?>\\.[^\\$beg_delim]*){0,}){0,})(?:$beg_delim(*ACCEPT:term))?(*PRUNE)(*FAIL)}sgc) {
elsif (m{\G$beg_delim((?>[^\\$beg_delim]*+(?>\\.[^\\$beg_delim]*){0,}){0,})}sgc) {
$string = $1 =~ s/\\([$beg_delim])/$1/gr;
}
# $REGMARK eq 'term'
(defined($pair_delim) ? /\G(?<=\Q$pair_delim\E)/ : /\G$beg_delim/gc)
|| $self->fatal_error(
error => sprintf(qq{can't find the quoted string terminator <<%s>>}, $pair_delim // $delim),
code => $_,
pos => $orig_pos,
);
$self->{line} += $string =~ s/\R\K//g if not $opt{no_count_line};
return $string;
}
## get_method_name() returns the following values:
# 1st: method/operator (or undef)
# 2nd: a true value if the operator requires an argument
# 3rd: type of operator (defined in $self->{hyper_ops})
sub get_method_name {
my ($self, %opt) = @_;
local *_ = $opt{code};
# Parse whitespace
$self->parse_whitespace(code => $opt{code});
# Alpha-numeric method name
if (/\G((?:SUPER::)*$self->{method_name_re})/goc) {
return ($1, 0, '');
}
# Super-script power
if (/\G(?=[⁰¹²³⁴⁵⁶⁷⁸⁹])/) {
return ('**', 1, 'op');
}
# Operator-like method name
if (m{\G$self->{operators_re}}goc) {
my ($key) = keys(%+);
return (
$+,
(
exists($self->{hyper_ops}{$key})
? $self->{hyper_ops}{$key}[0]
: not(exists $self->{postfix_ops}{$+})
),
$key
);
}
# Method name as expression
my ($obj) = $self->parse_expr(code => $opt{code});
return ({self => $obj // return}, 0, '');
}
sub parse_delim {
my ($self, %opt) = @_;
local *_ = $opt{code};
my @delims = ('|', keys(%{$self->{delim_pairs}}));
if (exists $opt{ignore_delim}) {
@delims = grep { not exists $opt{ignore_delim}{$_} } @delims;
}
my $regex = do {
local $" = "";
qr/\G([@delims])\h*/;
};
my $end_delim;
if (/$regex/gc) {
$end_delim = $self->{delim_pairs}{$1} // $1;
$self->parse_whitespace(code => $opt{code});
}
return $end_delim;
}
sub get_init_vars {
my ($self, %opt) = @_;
local *_ = $opt{code};
my $end_delim = $self->parse_delim(%opt);
my @vars;
my %classes;
while ( /\G(?<type>$self->{var_name_re}\h+$self->{var_name_re})\h*/goc
|| /\G([*:]?$self->{var_name_re})\h*/goc
|| (defined($end_delim) && /\G(?=[({])/)) {
my $declaration = $1;
if ($opt{with_vals} && defined($end_delim)) {
# Add the variable into the symbol table
if (defined $declaration) {
my ($name, $class_name) = $self->get_name_and_class((split(' ', $declaration))[-1]);
undef $classes{$class_name};
unshift @{$self->{vars}{$class_name}},
{
obj => '',
name => $name,
count => 0,
type => $opt{type},
line => $self->{line},
};
}
if (/\G<<?\h*/gc) {
my ($var) = /\G($self->{var_name_re})\h*/goc;
$var // $self->fatal_error(
code => $_,
pos => pos($_),
error => 'expected a subset name',
);
$declaration .= " < $var ";
}
if (/\G(?=\{)/) {
my $pos = pos($_);
$self->parse_block(code => $opt{code}, topic_var => 1);
$declaration .= substr($_, $pos, pos($_) - $pos);
}
elsif (/\G(?=\()/) {
my $pos = pos($_);
$self->parse_arg(code => $opt{code});
$declaration .= substr($_, $pos, pos($_) - $pos);
}
if (/$self->{var_init_sep_re}/goc) {
my $pos = pos($_);
$self->parse_obj(code => $opt{code}, multiline => 1);
$declaration .= '=' . substr($_, $pos, pos($_) - $pos);
}
}
push @vars, $declaration;
(defined($end_delim) && (/\G\h*,\h*/gc || /\G\h*(?:#.*)?+(?=\R)/gc)) || last;
$self->parse_whitespace(code => $opt{code});
}
# Remove the newly added variables
foreach my $class_name (keys %classes) {
for (my $i = 0 ; $i <= $#{$self->{vars}{$class_name}} ; $i++) {
if (ref($self->{vars}{$class_name}[$i]) eq 'HASH' and not ref($self->{vars}{$class_name}[$i]{obj})) {
splice(@{$self->{vars}{$class_name}}, $i--, 1);
}
}
}
$self->parse_whitespace(code => $opt{code});
defined($end_delim)
&& (
/\G\h*\Q$end_delim\E/gc
|| $self->fatal_error(
code => $_,
pos => pos($_),
error => "can't find the closing delimiter: `$end_delim`",
)
);
return \@vars;
}
sub parse_init_vars {
my ($self, %opt) = @_;
local *_ = $opt{code};
my $end_delim = $self->parse_delim(%opt);
my @var_objs;
while ( /\G(?<type>$self->{var_name_re})\h+($self->{var_name_re})\h*/goc
|| /\G([*:]?)($self->{var_name_re})\h*/goc
|| (defined($end_delim) && /\G(?=[({])/)) {
my ($attr, $name) = ($1, $2);
my $ref_type;
if (defined($+{type})) {
my $type = $+{type};
my $obj = $self->parse_expr(code => \$type);
if (not defined($obj) or ref($obj) eq 'HASH') {
$self->fatal_error(
code => $_,
pos => pos($_),
error => "invalid type <<$type>> for variable `$name`",
reason => "expected a type, such as: Str, Num, File, etc...",
);
}
$ref_type = $obj;
}
my ($subset);
if (ref($ref_type) eq 'Sidef::Variable::Subset') {
$subset = $ref_type;
undef $ref_type;
}
my ($var_name, $class_name) = $self->get_name_and_class($name);
if ($opt{type} eq 'del') {
my $var = $self->find_var($var_name, $class_name);
if (not defined($var)) {
$self->fatal_error(
code => $_,
pos => pos($_) - length($name),
var => ($class_name . '::' . $var_name),
error => "attempt to delete non-existent variable `$name`",
);
}
}
if (exists($self->{keywords}{$var_name}) or exists($self->{built_in_classes}{$var_name})) {
$self->fatal_error(
code => $_,
pos => $-[2],
error => "`$var_name` is either a keyword or a predefined variable!",
);
}
if (defined($end_delim) and m{\G<<?\h*}gc) {
my ($subset_name) = /\G($self->{var_name_re})/goc;
$subset_name // $self->fatal_error(
code => $_,
pos => pos($_),
error => "expected the name of the subset",
);
my $code = $subset_name;
my $obj = $self->parse_expr(code => \$code);
(defined($obj) and ref($obj) ne 'HASH')
|| $self->fatal_error(
code => $_,
pos => pos($_),
error => "expected a subset or a type",
);
$subset = $obj;
}
my ($value, $where_expr, $where_block);
if (defined($end_delim)) {
if (/\G\h*(?=\{)/gc) {
$where_block = $self->parse_block(code => $opt{code}, topic_var => 1);
}
elsif (/\G\h*(?=\()/gc) {
$where_expr = $self->parse_arg(code => $opt{code});
}
if (/$self->{var_init_sep_re}/goc) {
my $obj = $self->parse_obj(code => $opt{code}, multiline => 1);
$value = (
ref($obj) eq 'HASH'
? $obj
: {$self->{class} => [{self => $obj}]}
);
}
}
#<<<
my $obj = bless(
{
name => $var_name,
type => $opt{type},
(defined($ref_type) ? (ref_type => $ref_type) : ()),
(defined($subset) ? (subset => $subset) : ()),
class => $class_name,
defined($value) ? (value => $value, has_value => 1) : (),
defined($attr)
? ($attr eq '*' ? (array => 1, slurpy => 1)
: $attr eq ':' ? (hash => 1, slurpy => 1) : ())
: (),
defined($where_block) ? (where_block => $where_block) : (),
defined($where_expr) ? (where_expr => $where_expr) : (),
},
'Sidef::Variable::Variable'
);
#>>>
if (exists($opt{callback})) {
$opt{callback}->($obj);
}
if (!$opt{private} and $var_name ne '') {
unshift @{$self->{vars}{$class_name}},
{
obj => $obj,
name => $var_name,
count => 0,
type => $opt{type},
line => $self->{line},
};
}
if ($var_name eq '') {
$obj->{name} = '__ANON__' . refaddr($obj);
}
push @var_objs, $obj;
(defined($end_delim) && (/\G\h*,\h*/gc || /\G\h*(?:#.*)?+(?=\R)/gc)) || last;
if ($opt{params} and $obj->{slurpy}) {
$self->fatal_error(
error => "can't declare more parameters after a slurpy parameter",
code => $_,
pos => pos($_),
);
}
$self->parse_whitespace(code => $opt{code});
}
$self->parse_whitespace(code => $opt{code}) if defined($end_delim);
defined($end_delim)
&& (
/\G\h*\Q$end_delim\E/gc
|| $self->fatal_error(
code => $_,
pos => pos($_),
error => "can't find the closing delimiter: `$end_delim`",
)
);
return \@var_objs;
}
sub parse_whitespace {
my ($self, %opt) = @_;
my $beg_line = $self->{line};
my $found_space = -1;
local *_ = $opt{code};
{
++$found_space;
# Whitespace
if (/\G(?=\s)/) {
# Horizontal space
if (/\G\h+/gc) {
redo;
}
# Generic line
if (/\G\R/gc) {
++$self->{line};
# Here-document
while ($#{$self->{EOT}} != -1) {
my $eot = shift @{$self->{EOT}};
my $name = $eot->{name};
my $indent = $eot->{indent};
my $spaces = 0;
my $acc = '';
until (/\G$name(?:\R|\z)/gc) {
if (/\G(.*)/gc) {
$acc .= "$1\n";
}
# Indentation is true
if ($indent && /\G\R(\h*)$name(?:\R|\z)/gc) {
$spaces = length($1);
++$self->{line};
last;
}
/\G\R/gc
? ++$self->{line}
: $self->fatal_error(
error => "can't find string terminator <<$name>> anywhere before end-of-file",
code => $_,
pos => $eot->{pos},
line => $eot->{line},
);
}
if ($indent and $spaces > 0) {
$acc =~ s/^\h{1,$spaces}//gm;
}
++$self->{line};
push @{$eot->{obj}{$self->{class}}},
{
self => (
$eot->{type} == 0
? Sidef::Types::String::String->new($acc)
: Sidef::Types::String::String->new($acc)->apply_escapes($self)
)
};
}
/\G\h+/gc;
redo;
}
# Vertical space
if (/\G\v+/gc) { # should not reach here
redo;
}
}
# ZERO WIDTH SPACE
# https://www.fileformat.info/info/unicode/char/200b/index.htm
if (/\G\x{200B}+/gc) {
redo;
}
# Embedded comments (https://docs.raku.org/language/syntax#Multi-line_/_embedded_comments)
if (/\G#`(?=[[:punct:]])/gc) {
$self->get_quoted_string(code => $opt{code});
redo;
}
# One-line comment
if (/\G#.*/gc) {
redo;
}
# Multi-line C comment
if (m{\G/\*}gc) {
while (1) {
m{\G.*?\*/}gc && last;
/\G.+/gc || (/\G\R/gc ? $self->{line}++ : last);
}
redo;
}
if ($found_space > 0) {
return 1;
}
return;
}
}
sub parse_expr {
my ($self, %opt) = @_;
local *_ = $opt{code};
{
$self->parse_whitespace(code => $opt{code});
# End of an expression, or end of the script
if (/\G;/gc || /\G\z/) {
return;
}
if (/$self->{quote_operators_re}/goc) {
my ($double_quoted, $method, $package) = @{$^R};
pos($_) -= 1;
my ($string, $pos) = $self->get_quoted_string(code => $opt{code});
# Special case for array-like objects (bytes and chars)
my @array_like;
if ($method ne 'new' and $method ne '__NEW__') {
@array_like = ($package, $method);
$package = 'Sidef::Types::String::String';
$method = 'new';
}
if ($package eq 'Sidef::Module::Func' or $package eq 'Sidef::Module::OO') {
if ($string !~ /^$self->{var_name_re}\z/) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($string) - 1),
error => "invalid symbol declaration",
reason => "expected a variable-like name",
);
}
}
my $obj = (
$double_quoted
? do {
state $str = Sidef::Types::String::String->new; # load the string module
Sidef::Types::String::String::apply_escapes($package->$method($string), $self);
}
: $package->$method($string =~ s{\\\\}{\\}gr)
);
# Special case for backticks and Perl code (add method 'run')
if ($package eq 'Sidef::Types::Glob::Backtick' or $package eq 'Sidef::Types::Perl::Perl') {
my $struct =
$double_quoted && ref($obj) eq 'HASH'
? $obj
: {
$self->{class} => [
{
self => $obj,
call => [],
}
]
};
push @{$struct->{$self->{class}}[-1]{call}}, {method => 'run'};
$obj = $struct;
}
elsif (@array_like) {
if ($double_quoted and ref($obj) eq 'HASH') {
push @{$obj->{$self->{class}}[-1]{call}}, {method => $array_like[1]};
}
else {
my $method = $array_like[1];
$obj = $obj->$method;
}
}
return $obj;
}
# Object as expression
if (/\G(?=\()/) {
my $obj = $self->parse_arg(code => $opt{code});
return $obj;
}
# Block as object
if (/\G(?=\{)/) {
my $obj = $self->parse_block(code => $opt{code}, topic_var => 1);
return $obj;
}
# Array as object
if (/\G(?=\[)/) {
my @array;
my $obj = $self->parse_array(code => $opt{code});
if (ref($obj->{$self->{class}}) eq 'ARRAY') {
push @array, @{$obj->{$self->{class}}};
}
return bless(\@array, 'Sidef::Types::Array::HCArray');
}
# Bareword followed by a fat comma or preceded by a colon
if ( /\G:(\w+)/gc
|| /\G([^\W\d]\w*+)(?=\h*=>)/gc) {
# || /\G([^\W\d]\w*+)(?=\h*=>|:(?![=:]))/gc) {
return Sidef::Types::String::String->new($1);
}
# Bareword followed by a colon becomes a NamedParam with the bareword
# on the LHS
if (/\G([^\W\d]\w*+):(?![=:])/gc) {
my $name = $1;
my $obj = $self->parse_obj(code => $opt{code});
return Sidef::Variable::NamedParam->new($name, $obj);
}
# Declaration of variables (global and lexical)
if (/\G(var|global|del)\b\h*/gc) {
my $type = $1;
my $vars = $self->parse_init_vars(code => $opt{code}, type => $type);
my $init_obj = bless({vars => $vars}, 'Sidef::Variable::Init');
if (/\G\h*=\h*/gc) {
my $args = $self->parse_obj(code => $opt{code}, multiline => 1);
$args // $self->fatal_error(
code => $_,
pos => pos($_),
error => "expected an expression after variable declaration",
);
$init_obj->{args} = $args;
}
#if ($type eq 'del') {
# return bless {vars => []}, 'Sidef::Variable::Init';
#}
return $init_obj;
}
# "has" class attributes
if (exists($self->{current_class}) and /\Ghas\b\h*/gc) {
local $self->{allow_class_variable} = 0;
my $vars = $self->parse_init_vars(
code => $opt{code},
type => 'has',
private => 1,
);
foreach my $var (@{$vars}) {
my $name = $var->{name};
if (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name})) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
error => "`$name` is either a keyword or a predefined variable!",
);
}
}
my $args;
if (/\G\h*=\h*/gc) {
$args = $self->parse_obj(code => $opt{code}, multiline => 1);
$args // $self->fatal_error(
code => $_,
pos => pos($_) - 2,
error => qq{expected an expression after "=" in `has` declaration},
);
}
my $obj = bless {vars => $vars, defined($args) ? (args => $args) : ()}, 'Sidef::Variable::ClassAttr';
push @{$self->{current_class}{attributes}}, $obj;
return $obj;
}
# Declaration of constants and static variables
if (/\G(define|const|static)\b\h*/gc) {
my $type = $1;
my $line = $self->{line};
my @var_objs;
my $callback = sub {
my ($v) = @_;
my $name = $v->{name};
my $class_name = $v->{class};
my $var = (
$type eq 'define' ? bless($v, 'Sidef::Variable::Define')
: $type eq 'static' ? bless($v, 'Sidef::Variable::Static')
: $type eq 'const' ? bless($v, 'Sidef::Variable::Const')
: die "[PARSER ERROR] Invalid variable type: $type"
);
push @var_objs, $var;
unshift @{$self->{vars}{$class_name}},
{
obj => $var,
name => $name,
count => 0,
type => $type,
line => $line,
};
};
my $vars = $self->parse_init_vars(
code => $opt{code},
type => $type,
private => 1,
callback => $callback,
);
foreach my $var (@var_objs) {
my $name = $var->{name};
if (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name})) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
error => "`$name` is either a keyword or a predefined variable!",
);
}
}
if (@var_objs == 1 and /\G\h*=\h*/gc) {
my $var = $var_objs[0];
my $obj = $self->parse_obj(code => $opt{code}, multiline => 1);
$obj // $self->fatal_error(
code => $_,
pos => pos($_) - 2,
error => qq{expected an expression after $type "$var->{name}"},
);
$var->{value} = $obj;
}
my $const_init = bless({vars => \@var_objs, type => $type}, 'Sidef::Variable::ConstInit');
if (/\G\h*=\h*/gc) {
$self->fatal_error(
code => $_,
pos => pos($_) - 2,
error => qq{the correct syntax is: `$type(x = ..., y = ...)`},
);
}
return $const_init;
}
# Struct declaration
if (/\Gstruct\b\h*/gc) {
my ($name, $class_name);
if (/\G($self->{var_name_re})\h*/goc) {
($name, $class_name) = $self->get_name_and_class($1);
}
if (defined($name) and (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name}))) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
error => "`$name` is either a keyword or a predefined variable!",
);
}
my $struct = bless(
{
name => $name,
class => $class_name,
},
'Sidef::Variable::Struct'
);
if (defined $name) {
unshift @{$self->{vars}{$class_name}},
{
obj => $struct,
name => $name,
count => 0,
type => 'struct',
line => $self->{line},
};
}
my $vars =
$self->parse_init_vars(
code => $opt{code},
type => 'var',
private => 1,
);
$struct->{vars} = $vars;
return $struct;
}
# Subset declaration
if (/\Gsubset\b\h*/gc) {
my ($name, $class_name);
if (/\G($self->{var_name_re})\h*/goc) {
($name, $class_name) = $self->get_name_and_class($1);
}
else {
$self->fatal_error(
code => $_,
pos => pos($_),
error => "expected a name after the keyword 'subset'",
);
}
if (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name})) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
error => "`$name` is either a keyword or a predefined variable!",
);
}
my $subset = bless({name => $name, class => $class_name}, 'Sidef::Variable::Subset');
unshift @{$self->{vars}{$class_name}},
{
obj => $subset,
name => $name,
count => 0,
type => 'subset',
line => $self->{line},
};
# Inheritance
if (/\G<<?\h*/gc) {
{
my ($name) = /\G($self->{var_name_re})\h*/goc;
$name // $self->fatal_error(
code => $_,
pos => pos($_),
error => "expected a type name for subsetting",
);
my $code = $name;
my $type = $self->parse_expr(code => \$code);
push @{$subset->{inherits}}, $type;
/\G,\h*/gc && redo;
}
}
if (/\G(?=\{)/) {
my $block = $self->parse_block(code => $opt{code}, topic_var => 1);
$subset->{block} = $block;
}
return $subset;
}
# Declaration of enums
if (/\Genum\b\h*/gc) {
my $vars =
$self->parse_init_vars(
code => $opt{code},
type => 'var',
private => 1,
);
@{$vars}
|| $self->fatal_error(
code => $_,
pos => pos($_),
error => q{expected one or more variable names after <enum>},
);
my $value = Sidef::Types::Number::Number::_set_int(-1);
foreach my $var (@{$vars}) {
my $name = $var->{name};
if (ref $var->{value} eq 'HASH') {
$var->{value} = $var->{value}{$self->{class}}[-1]{self};
}
$value =
$var->{has_value}
? $var->{value}
: $value->inc;
if (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name})) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
error => "`$name` is either a keyword or a predefined variable!",
);
}
unshift @{$self->{vars}{$self->{class}}},
{
obj => $value,
name => $name,
count => 0,
type => 'enum',
line => $self->{line},
};
}
return $value;
}
# Local variables
if (/\Glocal\b\h*/gc) {
my $expr = $self->parse_obj(code => $opt{code});
return bless({expr => $expr}, 'Sidef::Variable::Local');
}
# Declaration of classes, methods and functions
if (
/\G(func|class)\b\h*/gc
|| /\G(->)\h*/gc
|| (exists($self->{current_class})
&& /\G(method)\b\h*/gc)
) {
my $beg_pos = $-[0];
my $type =
$1 eq '->'
? exists($self->{current_class}) && !(exists($self->{current_method}))
? 'method'
: 'func'
: $1;
my $name = '';
my $class_name = $self->{class};
my $built_in_obj;
if ($type eq 'class' and /\G($self->{var_name_re})\h*/gco) {
$name = $1;
if (exists($self->{built_in_classes}{$name}) and /\G(?=[{<])/) {
my ($obj) = $self->parse_expr(code => \$name);
if (defined($obj)) {
$name = '';
$built_in_obj = $obj;
}
}
else {
($name, $class_name) = $self->get_name_and_class($1);
}
}
if ($type eq 'method') {
$name = (
/\G($self->{method_name_re})\h*/goc ? $1
: /\G($self->{operators_re})\h*/goc ? $+
: ''
);
($name, $class_name) = $self->get_name_and_class($name);
}
elsif ($type ne 'class') {
$name = /\G($self->{var_name_re})\h*/goc ? $1 : '';
($name, $class_name) = $self->get_name_and_class($name);
}
if ( $type ne 'method'
and $type ne 'class'
and (exists($self->{keywords}{$name}) or exists($self->{built_in_classes}{$name}))) {
$self->fatal_error(
code => $_,
pos => $-[0],
error => "`$name` is either a keyword or a predefined variable!",
);
}
my $obj =
($type eq 'func' or $type eq 'method') ? bless({name => $name, type => $type, class => $class_name}, 'Sidef::Variable::Variable')
: $type eq 'class' ? bless({name => ($built_in_obj // $name), class => $class_name}, 'Sidef::Variable::ClassInit')
: $self->fatal_error(
error => "invalid type",
reason => "expected a magic thing to happen",
code => $_,
pos => pos($_),
);
if ($name ne '') {
my $var = $self->find_var($name, $class_name);
if (defined($var) and $var->{type} eq 'class') {
$obj->{parent} = $var->{obj};
push @{$obj->{inherit}}, ref($var->{obj}{name}) ? $var->{obj}{name} : $var->{obj};
}
}
my $has_kids = 0;
my $parent;
if (($type eq 'method' or $type eq 'func') and $name ne '') {
my $var = $self->find_var($name, $class_name);
# A function or a method must be declared in the same scope
if (defined($var) and $var->{obj}{type} eq $type) {
$parent = $var->{obj};
$has_kids = 1;
#~ push @{$var->{obj}{value}{kids}}, $obj;
$parent->{has_kids} = 1;
$obj->{parent} = $parent;
}
}
if (not $has_kids) {
unshift @{$self->{vars}{$class_name}},
{
obj => $obj,
name => $name,
count => 0,
type => $type,
line => $self->{line},
};
}
if ($type eq 'class') {
my $var_names =
$self->parse_init_vars(
code => $opt{code},
params => 1,
private => 1,
type => 'has',
ignore_delim => {
'{' => 1,
'<' => 1,
},
);
# Set the class parameters
$obj->{vars} = $var_names;
# Class inheritance (class Name(...) << Name1, Name2)
if (/\G\h*<<?\h*/gc) {
while (/\G($self->{var_name_re})\h*/gco) {
my ($name, $class_name) = $self->get_name_and_class($1);
if (defined(my $class = $self->find_var($name, $class_name))) {
if ($class->{type} eq 'class') {
# Detect inheritance from the same class
if (refaddr($obj) == refaddr($class->{obj})) {
$self->fatal_error(
error => "Inheriting from the same class is not allowed",
code => $_,
pos => pos($_) - length($name) - 1,
);
}
++$class->{count};
push @{$obj->{inherit}}, $class->{obj};
}
else {
$self->fatal_error(
error => "this is not a class",
reason => "expected a class name",
code => $_,
pos => pos($_) - length($name) - 1,
);
}
}
elsif (exists $self->{built_in_classes}{$name}) {
$self->fatal_error(
error => "Inheriting from built-in classes is not supported",
reason => "`$name` is a built-in class",
code => $_,
pos => pos($_) - length($name) - 1,
);
}
else {
$self->fatal_error(
error => "can't find `$name` class",
reason => "expected an existent class name",
var => ($class_name . '::' . $name),
code => $_,
pos => pos($_) - length($name) - 1,
);
}
/\G,\h*/gc;
}
}
/\G\h*(?=\{)/gc
|| $self->fatal_error(
error => "invalid class declaration",
reason => "expected: class $name(...){...}",
code => $_,
pos => pos($_)
);
#~ if (ref($built_in_obj) eq 'Sidef::Variable::ClassInit') {
#~ $obj->{name} = $built_in_obj->{name};
#~ }
local $self->{class_name} = (defined($built_in_obj) ? ref($built_in_obj) : $obj->{name});
local $self->{current_class} = $built_in_obj // $obj;
my $block = $self->parse_block(code => $opt{code});
# Set the block of the class
$obj->{block} = $block;
}
if ($type eq 'func' or $type eq 'method') {
my $var_names = do {
local $self->{allow_class_variable} = 1 if $type eq 'method';
$self->get_init_vars(
code => $opt{code},
with_vals => 1,
ignore_delim => {
'{' => 1,
'-' => 1,
}
);
};
# Functions and method traits (example: "is cached")
if (/\G\h*is\h+(?=\w)/gc) {
while (/\G(\w+)/gc) {
my $trait = $1;
if ($trait eq 'cached') {
$obj->{cached} = 1;
}
#elsif ($type eq 'method' and $trait eq 'exported') {
# $obj->{exported} = 1;
#}
else {
$self->fatal_error(
error => "Unknown $type trait: $trait",
code => $_,
pos => pos($_),
);
}
/\G\h*,\h*/gc || last;
}
}
# Function return type (func name(...) -> Type {...})
if (/\G\h*->\h*/gc) {
my @ref;
if (/\G\(/gc) { # multiple types
while (1) {
my ($ref) = $self->parse_expr(code => $opt{code});
push @ref, $ref;
/\G\s*\)/gc && last;
/\G\s*,\s*/gc
|| $self->fatal_error(
error => "invalid return-type for $type $self->{class_name}<<$name>>",
reason => "expected a comma",
code => $_,
pos => pos($_),
);
}
}
else { # only one type
my ($ref) = $self->parse_expr(code => $opt{code});
push @ref, $ref;
}
foreach my $ref (@ref) {
if (ref($ref) eq 'HASH') {
$self->fatal_error(
error => "invalid return-type for $type $self->{class_name}<<$name>>",
reason => "expected a valid type, such as: Str, Num, Arr, etc...",
code => $_,
pos => pos($_),
);
}
}
$obj->{returns} = \@ref;
}
/\G\h*\{\h*/gc
|| $self->fatal_error(
error => "invalid `$type` declaration",
reason => "expected: $type $name(...){...}",
code => $_,
pos => pos($_)
);
local $self->{$type eq 'func' ? 'current_function' : 'current_method'} = $has_kids ? $parent : $obj;
my $args = '|' . join(',', $type eq 'method' ? 'self' : (), @{$var_names}) . ' |';
my $code = '{' . $args . substr($_, pos);
my $block = $self->parse_block(code => \$code, with_vars => 1);
pos($_) += pos($code) - length($args) - 1;
# Set the block of the function/method
$obj->{value} = $block;
}
return $obj;
}
# "given(expr) {...}" construct
if (/\Ggiven\b\h*/gc) {
my $expr = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
$expr // $self->fatal_error(
error => "invalid declaration of the `given/when` construct",
reason => "expected `given(expr) {...}`",
code => $_,
pos => pos($_),
);
my $given_obj = bless({expr => $expr}, 'Sidef::Types::Block::Given');
local $self->{current_given} = $given_obj;
my $block = (
/\G\h*(?=\{)/gc
? $self->parse_block(code => $opt{code}, topic_var => 1)
: $self->fatal_error(
error => "expected a block after `given(expr)`",
code => $_,
pos => pos($_),
)
);
$given_obj->{block} = $block;
return $given_obj;
}
# "when(expr) {...}" construct
if (exists($self->{current_given}) && /\Gwhen\b\h*/gc) {
my $expr = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
$expr // $self->fatal_error(
error => "invalid declaration of the `when` construct",
reason => "expected `when(expr) {...}`",
code => $_,
pos => pos($_),
);
my $block = (
/\G\h*(?=\{)/gc
? $self->parse_block(code => $opt{code}, with_vars => 1)
: $self->fatal_error(
error => "expected a block after `when(expr)`",
code => $_,
pos => pos($_),
)
);
return bless({expr => $expr, block => $block}, 'Sidef::Types::Block::When');
}
# "case(expr) {...}" construct
if (exists($self->{current_given}) && /\Gcase\b\h*/gc) {
my $expr = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
$expr // $self->fatal_error(
error => "invalid declaration of the `case` construct",
reason => "expected `case(expr) {...}`",
code => $_,
pos => pos($_),
);
my $block = (
/\G\h*(?=\{)/gc
? $self->parse_block(code => $opt{code}, with_vars => 1)
: $self->fatal_error(
error => "expected a block after `case(expr)`",
code => $_,
pos => pos($_),
)
);
return bless({expr => $expr, block => $block}, 'Sidef::Types::Block::Case');
}
# "default {...}" or "else { ... }" construct for `given/when`
if (exists($self->{current_given}) && /\G(?:default|else)\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
return bless({block => $block}, 'Sidef::Types::Block::Default');
}
# `continue` keyword inside a given/when construct
if (exists($self->{current_given}) && /\Gcontinue\b/gc) {
state $x = bless({}, 'Sidef::Types::Block::Continue');
return $x;
}
# "do {...}" construct
if (/\Gdo\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
return bless({block => $block}, 'Sidef::Types::Block::Do');
}
# "loop {...}" construct
if (/\Gloop\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
return bless({block => $block}, 'Sidef::Types::Block::Loop');
}
# "try/catch" construct
if (/\Gtry\h*(?=\{)/gc) {
my $try_block = $self->parse_block(code => $opt{code});
my $obj = bless({try => $try_block}, 'Sidef::Types::Block::Try');
$self->parse_whitespace(code => $opt{code});
if (/\Gcatch\h*(?=\{)/gc) {
$obj->{catch} = $self->parse_block(code => $opt{code}, with_vars => 1);
}
else {
$self->backtrack_whitespace(code => $opt{code});
}
return $obj;
}
# "gather/take" construct
if (/\Ggather\h*(?=\{)/gc) {
my $obj = bless({}, 'Sidef::Types::Block::Gather');
local $self->{current_gather} = $obj;
my $block = $self->parse_block(code => $opt{code});
$obj->{block} = $block;
return $obj;
}
if (exists($self->{current_gather}) and /\Gtake\b\h*/gc) {
my $obj = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
return bless({expr => $obj, gather => $self->{current_gather}}, 'Sidef::Types::Block::Take');
}
# Declaration of a module
if (/\Gmodule\b\h*/gc) {
my $name =
/\G($self->{var_name_re})\h*/goc
? $1
: $self->fatal_error(
error => "invalid module declaration",
reason => "expected a name",
code => $_,
pos => pos($_)
);
$self->parse_whitespace(code => $opt{code});
if (/\G(?=\{)/) {
my $prev_class = $self->{class};
local $self->{class} = $name;
my $obj = $self->parse_block(code => $opt{code}, is_module => 1, prev_class => $prev_class);
return
bless {
name => $name,
block => $obj
},
'Sidef::Meta::Module';
}
else {
$self->fatal_error(
error => "invalid module declaration",
reason => "expected: module $name {...}",
code => $_,
pos => pos($_)
);
}
}
if (/\Gimport\b\h*/gc) {
my $import_pos = pos($_);
my $var_names =
$self->get_init_vars(code => $opt{code},
with_vals => 0);
$self->backtrack_whitespace(code => $opt{code});
@{$var_names}
|| $self->fatal_error(
code => $_,
pos => $import_pos,
error => "expected a variable-like name for importing!",
);
foreach my $var_name (@{$var_names}) {
my ($name, $class) = $self->get_name_and_class($var_name);
if ($class eq ($self->{class})) {
$self->fatal_error(
code => $_,
pos => $import_pos,
error => "can't import '${class}::${name}' into the same namespace",
);
}
my $var = $self->find_var($name, $class);
if (not defined $var) {
$self->fatal_error(
code => $_,
pos => $import_pos,
error => "variable '${class}::${name}' does not exists",
);
}
$var->{count}++;
unshift @{$self->{vars}{$self->{class}}},
{
obj => $var->{obj},
name => $name,
count => 0,
type => $var->{type},
line => $self->{line},
};
}
return 1;
}
if (/\Ginclude\b\h*/gc) {
my $include_pos = pos($_);
state $x = do {
require Cwd;
require File::Spec;
require File::Basename;
};
if (@{$self->{inc}} == 0) {
push @{$self->{inc}}, split(':', $ENV{SIDEF_INC}) if exists($ENV{SIDEF_INC});
push @{$self->{inc}}, File::Spec->catdir(File::Basename::dirname(Cwd::abs_path($0)), File::Spec->updir, 'share', 'sidef');
if (-f $self->{script_name}) {
push @{$self->{inc}}, File::Basename::dirname(Cwd::abs_path($self->{script_name}));
}
push @{$self->{inc}}, File::Spec->curdir;
}
my @abs_filenames;
if (/\G($self->{var_name_re})/gc) {
my $var_name = $1;
# The module is defined in the current file -- skip
if (exists $self->{ref_vars}{$var_name}) {
redo;
}
# The module was already included -- skip
if (exists $Sidef::INCLUDED{$var_name}) {
redo;
}
my @path = split(/::/, $var_name);
my $mod_path = File::Spec->catfile(@path[0 .. $#path - 1], $path[-1] . '.sm');
$Sidef::INCLUDED{$var_name} = $mod_path;
my ($full_path, $found_module);
foreach my $inc_dir (@{$self->{inc}}) {
if ( -e ($full_path = File::Spec->catfile($inc_dir, $mod_path))
and -f _
and -r _ ) {
$found_module = 1;
last;
}
}
$found_module // $self->fatal_error(
code => $_,
pos => $include_pos,
error => "can't find the module '${mod_path}' anywhere in ['" . join("', '", @{$self->{inc}}) . "']",
);
push @abs_filenames, [$full_path, $var_name];
}
else {
my $orig_dir = Cwd::getcwd();
my $orig_file = Cwd::abs_path($self->{file_name});
my $file_dir = File::Basename::dirname($orig_file);
my $chdired = 0;
if ($orig_dir ne $file_dir) {
if (chdir($file_dir)) {
$chdired = 1;
}
}
my $expr = do {
my ($obj) = $self->parse_expr(code => $opt{code});
$obj;
};
my @files = (
ref($expr) eq 'HASH'
? do {
map { $_->{self} }
map { @{$_->{self}->{$self->{class}}} }
map { @{$expr->{$_}} }
keys %{$expr};
}
: $expr
);
push @abs_filenames, map {
my $filename = $_;
if (index(ref($filename), 'Sidef::') == 0) {
$filename = $filename->get_value;
}
ref($filename) ne ''
and $self->fatal_error(
code => ${$opt{code}},
pos => $include_pos,
error => 'include-error: invalid value of type "' . ref($filename) . '" (expected a string)',
);
my @files;
foreach my $file (glob($filename)) {
my $resolved = 0;
foreach my $base ('', @{$self->{inc}}) {
my $abs_path = File::Spec->rel2abs($file, $base) // next;
if (-f $abs_path) {
push @files, $abs_path;
$resolved = 1;
last;
}
}
if (!$resolved) {
$self->fatal_error(
code => ${$opt{code}},
pos => $include_pos,
error => "include-error: could not resolve path to file `$file`",
);
}
}
foreach my $file (@files) {
if (exists $Sidef::INCLUDED{$file}) {
$self->fatal_error(
code => ${$opt{code}},
pos => $include_pos,
error => "include-error: circular inclusion of file `$file`",
);
}
}
map { [$_] } @files
} @files;
if ($chdired) { chdir($orig_dir) }
}
my @included;
foreach my $pair (@abs_filenames) {
my ($full_path, $name) = @{$pair};
open(my $fh, '<:utf8', $full_path)
|| $self->fatal_error(
code => ${$opt{code}},
pos => $include_pos,
error => "can't open file `$full_path`: $!"
);
my $content = do { local $/; <$fh> };
close $fh;
next if $Sidef::INCLUDED{$full_path};
local $self->{class} = $name if defined $name; # new namespace
local $self->{line} = 1;
local $self->{file_name} = $full_path;
local $Sidef::INCLUDED{$full_path} = 1;
my $ast = $self->parse_script(code => \$content);
push @included,
{
name => $name,
file => $full_path,
ast => $ast,
};
}
return bless({included => \@included}, 'Sidef::Meta::Included');
}
# Super-script power
if (/\G([⁰¹²³⁴⁵⁶⁷⁸⁹]+)/gc) {
my $num = ($1 =~ tr/⁰¹²³⁴⁵⁶⁷⁸⁹/0-9/r);
return Sidef::Types::Number::Number::_set_int($num);
}
# Binary, hexadecimal and octal numbers
if (/\G0(b[10_]*|x[0-9A-Fa-f_]*|o[0-7_]*|[0-7_]+)\b/gc) {
my $num = $1 =~ tr/_//dr;
return
Sidef::Types::Number::Number->new(
$num =~ /^b/ ? (substr($num, 1) || 0, 2)
: $num =~ /^o/ ? (substr($num, 1) || 0, 8)
: $num =~ /^x/ ? (substr($num, 1) || 0, 16)
: ($num || 0, 8)
);
}
# Integer or float number
if (/\G((?=\.?[0-9])[0-9_]*+(?:\.[0-9_]++)?(?:[Ee](?:[+-]?+[0-9_]+))?)/gc) {
my $num = $1 =~ tr/_//dr;
if (/\Gi\b/gc) { # imaginary
return Sidef::Types::Number::Complex->new(0, $num);
}
elsif (/\Gf\b/gc) { # floating-point
return Sidef::Types::Number::Number::_set_str('float', $num);
}
return (
$num =~ /^-?[0-9]+\z/
? Sidef::Types::Number::Number::_set_int($num)
: Sidef::Types::Number::Number->new($num)
);
}
# Prefix `...`
if (/\G\.\.\./gc) {
return
bless(
{
line => $self->{line},
file => $self->{file_name},
},
'Sidef::Meta::Unimplemented'
);
}
# Implicit method call on special variable: "_"
if (/\G\./) {
if (defined(my $var = $self->find_var('_', $self->{class}))) {
$var->{count}++;
return $var->{obj};
}
$self->fatal_error(
code => $_,
pos => pos($_),
error => q{attempt of using an implicit method call on an inexistent "_" variable},
);
}
# Quoted words, numbers, vectors and matrices
# %w(...), %i(...), %n(...), %v(...), %m(...), «...», <...>
if (/\G%([wWinvm])\b/gc || /\G(?=(«|<(?!<)))/) {
my ($type) = $1;
my $strings = $self->get_quoted_words(code => $opt{code});
if ($type eq 'w' or $type eq '<') {
my @list = map { Sidef::Types::String::String->new(s{\\(?=[\\#\s])}{}gr) } @{$strings};
return Sidef::Types::Array::Array->new(\@list);
}
if ($type eq 'i') {
my @list = map { Sidef::Types::Number::Number->new(s{\\(?=[\\#\s])}{}gr)->int }
split(/[\s,]+/, join(' ', @$strings));
return Sidef::Types::Array::Array->new(\@list);
}
if ($type eq 'n') {
my @list =
map { Sidef::Types::Number::Number->new(s{\\(?=[\\#\s])}{}gr) } split(/[\s,]+/, join(' ', @$strings));
return Sidef::Types::Array::Array->new(\@list);
}
if ($type eq 'v') {
my @list =
map { Sidef::Types::Number::Number->new(s{\\(?=[\\#\s])}{}gr) } split(/[\s,]+/, join(' ', @$strings));
return Sidef::Types::Array::Vector->new(@list); # this must be passed as a list
}
if ($type eq 'm') {
my @matrix;
my $data = join(' ', @$strings);
foreach my $line (split(/\s*;\s*/, $data)) {
my @row = map { Sidef::Types::Number::Number->new(s{\\(?=[\\#\s])}{}gr) } split(/[\s,]+/, $line);
push @matrix, Sidef::Types::Array::Array->new(\@row);
}
return Sidef::Types::Array::Matrix->new(@matrix);
}
my ($inline_expression, @objs);
foreach my $item (@{$strings}) {
my $str = Sidef::Types::String::String->new($item)->apply_escapes($self);
$inline_expression ||= ref($str) eq 'HASH';
push @objs, $str;
}
return (
$inline_expression
? bless([map { {self => $_} } @objs], 'Sidef::Types::Array::HCArray')
: Sidef::Types::Array::Array->new(\@objs)
);
}
# Prefix method call (`::name(...)` or `::name ...`)
if (/\G::($self->{var_name_re})\h*/goc) {
my $name = $1;
my $pos = pos($_);
my $arg = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
if (not exists($arg->{$self->{class}})) {
$self->fatal_error(
code => $_,
pos => ($pos - length($name)),
var => $name,
error => "attempt to call method <$name> on an undefined value",
);
}
return
bless {
name => $name,
expr => $arg,
},
'Sidef::Meta::PrefixMethod';
}
if (/($self->{prefix_obj_re})\h*/goc) {
return ($^R, 1, $1);
}
# Assertions
if (/\G(assert(?:_(?:eq|ne))?+)\b\h*/gc) {
my $action = $1;
my $arg = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
return
bless(
{
arg => $arg,
act => $action,
line => $self->{line},
file => $self->{file_name},
},
'Sidef::Meta::Assert'
);
}
# die/warn
if (/\G(die|warn)\b\h*/gc) {
my $action = $1;
my $arg = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
return
bless(
{
arg => $arg,
line => $self->{line},
file => $self->{file_name},
},
$action eq 'die'
? "Sidef::Meta::Error"
: "Sidef::Meta::Warning"
);
}
# Eval keyword
if (/\Geval\b\h*/gc) {
my $obj = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
#<<<
return bless(
{
expr => $obj,
parser => Sidef::Object::Object::dclone(scalar {%$self}),
#vars => {$self->{class} => [@{$self->{vars}{$self->{class}}}]},
#ref_vars_refs => {$self->{class} => [@{$self->{ref_vars_refs}{$self->{class}}}]},
}, 'Sidef::Eval::Eval');
#>>>
}
if (/\GParser\b/gc) {
return $self;
}
# Regular expression
if (m{\G(?=/)} || /\G%r\b/gc) {
my $string = $self->get_quoted_string(code => $opt{code});
return Sidef::Types::Regex::Regex->new($string, /\G($self->{match_flags_re})/goc ? $1 : undef);
}
# Class variable in form of `Class!var_name`
if (/\G($self->{var_name_re})!($self->{var_name_re})/goc) {
my ($class_name, $var_name) = ($1, $2);
my $class_obj = $self->parse_expr(code => \$class_name);
return (bless {class => $class_obj, name => $var_name}, 'Sidef::Variable::ClassVar');
}
# Static object (like String or nil)
if (/$self->{static_obj_re}/goc) {
return $^R;
}
if (/\G__MAIN__\b/gc) {
if (-e $self->{script_name}) {
state $x = require Cwd;
return Sidef::Types::String::String->new(Cwd::abs_path($self->{script_name}));
}
return Sidef::Types::String::String->new($self->{script_name});
}
if (/\G__FILE__\b/gc) {
if (-e $self->{file_name}) {
state $x = require Cwd;
return Sidef::Types::String::String->new(Cwd::abs_path($self->{file_name}));
}
return Sidef::Types::String::String->new($self->{file_name});
}
if (/\G__DATE__\b/gc) {
my (undef, undef, undef, $day, $mon, $year) = localtime;
return Sidef::Types::String::String->new(join('-', $year + 1900, map { sprintf "%02d", $_ } $mon + 1, $day));
}
if (/\G__TIME__\b/gc) {
my ($sec, $min, $hour) = localtime;
return Sidef::Types::String::String->new(join(':', map { sprintf "%02d", $_ } $hour, $min, $sec));
}
if (/\G__LINE__\b/gc) {
return Sidef::Types::Number::Number->new($self->{line});
}
if (/\G__COMPILED__\b/gc) {
return Sidef::Types::Bool::Bool->new($self->{opt}{R} eq 'Perl' or $self->{opt}{c});
}
if (/\G__OPTIMIZED__\b/gc) {
return Sidef::Types::Bool::Bool->new(($self->{opt}{O} || 0) > 0);
}
if (/\G__(?:END|DATA)__\b\h*+\R?/gc) {
if (exists $self->{'__DATA__'}) {
$self->{'__DATA__'} = substr($_, pos);
}
pos($_) = length($_);
return;
}
if (/\GDATA\b/gc) {
return (
$self->{static_objects}{'__DATA__'} //= do {
bless({data => \$self->{'__DATA__'}}, 'Sidef::Meta::Glob::DATA');
}
);
}
# Beginning of a here-document (<<"EOT", <<'EOT', <<EOT)
if (/\G<<(-)?+(?=\S)/gc) {
my $indent = $1 ? 1 : 0;
my ($name, $type) = (undef, 1);
my $pos = pos($_);
if (/\G(?=(['"„]))/) {
$type = 0 if $1 eq q{'};
my $str = $self->get_quoted_string(code => $opt{code});
$name = $str;
}
elsif (/\G(\w+)/gc) {
$name = $1;
}
else {
$self->fatal_error(
error => "invalid 'here-doc' declaration",
reason => "expected an alpha-numeric token after '<<'",
code => $_,
pos => pos($_)
);
}
my $obj = {$self->{class} => []};
push @{$self->{EOT}},
{
name => $name,
indent => $indent,
type => $type,
obj => $obj,
pos => $pos,
line => $self->{line},
};
return $obj;
}
if (exists($self->{current_block}) && /\G__BLOCK__\b/gc) {
return $self->{current_block};
}
if (/\G__NAMESPACE__\b/gc) {
return Sidef::Types::String::String->new($self->{class});
}
if (exists($self->{current_function})) {
/\G__FUNC__\b/gc && return $self->{current_function};
/\G__FUNC_NAME__\b/gc && return Sidef::Types::String::String->new($self->{current_function}{name});
}
if (exists($self->{current_class})) {
/\G__CLASS__\b/gc && return $self->{current_class};
/\G__CLASS_NAME__\b/gc && return Sidef::Types::String::String->new($self->{class_name});
}
if (exists($self->{current_method})) {
/\G__METHOD__\b/gc && return $self->{current_method};
/\G__METHOD_NAME__\b/gc && return Sidef::Types::String::String->new($self->{current_method}{name});
}
# Variable access
if (/\G($self->{var_name_re})/goc) {
my $len_var = length($1);
my ($name, $class) = $self->get_name_and_class($1);
if (defined(my $var = $self->find_var($name, $class))) {
if ($var->{type} eq 'del') {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
var => ($class . '::' . $name),
error => "attempt to use the deleted variable <$name>",
);
}
$var->{count}++;
return $var->{obj};
}
if ($name eq 'ARGV' or $name eq 'ENV') {
my $type = 'var';
my $variable = bless({name => $name, type => $type, class => $class}, 'Sidef::Variable::Variable');
unshift @{$self->{vars}{$class}},
{
obj => $variable,
name => $name,
count => 1,
type => $type,
line => $self->{line},
};
return $variable;
}
# Class instance variables
if (
ref($self->{current_class}) eq 'Sidef::Variable::ClassInit'
and defined(
my $var = (first { $_->{name} eq $name } (@{$self->{current_class}{vars}}, map { @{$_->{vars}} } @{$self->{current_class}{attributes}}))
)
) {
if (exists $self->{current_method}) {
if (defined(my $var = $self->find_var('self', $class))) {
if ($self->{opt}{k}) {
print STDERR "[INFO] `$name` is parsed as `self.$name` at $self->{file_name} line $self->{line}\n";
}
$var->{count}++;
return
scalar {
$self->{class} => [
{
self => $var->{obj},
ind => [{hash => [$name]}],
}
]
};
}
}
elsif (exists $self->{allow_class_variable}) {
return $var;
}
elsif ($var->{type} eq 'has') {
$self->fatal_error(
error => "class variable <<$var->{name}>> can't be used outside a method",
pos => (pos($_) - length($name)),
var => $var,
code => $_,
);
}
else { # this should not happen
$self->fatal_error(
error => "can't use undeclared variable <<$var->{name}>> in this context",
pos => (pos($_) - length($name)),
var => $var,
code => $_,
);
}
}
if (/\G(?=\h*:?=(?![=~>]))/) {
if (not $self->{interactive}) {
warn "[WARNING] Implicit declaration of global variable `$name`" . " at $self->{file_name} line $self->{line}\n";
}
my $code = "global $name";
return $self->parse_expr(code => \$code);
}
# Method call in functional style (deprecated -- use `::name()` instead)
if ($len_var == length($name)) {
if ($self->{opt}{k}) {
print STDERR "[INFO] `$name` is parsed as a prefix method-call at $self->{file_name} line $self->{line}\n";
}
if ($self->{allow_class_variable}) {
return $name;
}
my $pos = pos($_);
my $arg = (
/\G\h*+(?=\()/gc
? $self->parse_arg(code => $opt{code})
: $self->fatal_error(
code => $_,
pos => ($pos - length($name)),
var => ($class . '::' . $name),
error => "variable <$name> is not declared in the current scope",
)
);
if (not exists($arg->{$self->{class}})) {
$self->fatal_error(
code => $_,
pos => ($pos - length($name)),
var => $name,
error => "attempt to call method <$name> on an undefined value",
);
}
return
bless {
name => $name,
expr => $arg,
},
'Sidef::Meta::PrefixMethod';
}
# Undeclared variable
$self->fatal_error(
code => $_,
pos => (pos($_) - length($name)),
var => ($class . '::' . $name),
error => "variable <$class\::$name> is not declared in the current scope",
);
}
# Regex variables ($1, $2, ...)
if (/\G\$([0-9]+)\b/gc) {
$self->fatal_error(
code => $_,
pos => (pos($_) - length($1)),
error => "regex capture-variables are not supported",
);
}
/\G\$/gc && redo;
#warn "$self->{script_name}:$self->{line}: unexpected char: " . substr($_, pos($_), 1) . "\n";
#return undef, pos($_) + 1;
return;
}
}
sub parse_arg {
my ($self, %opt) = @_;
local *_ = $opt{code};
if (/\G\(/gc) {
my $p = pos($_);
local $self->{parentheses} = 1;
my $obj = $self->parse_script(code => $opt{code});
$self->{parentheses}
&& $self->fatal_error(
code => $_,
pos => $p - 1,
error => "unbalanced parenthesis",
);
return $obj;
}
return;
}
sub parse_array {
my ($self, %opt) = @_;
local *_ = $opt{code};
if (/\G\[/gc) {
my $p = pos($_);
local $self->{right_brackets} = 1;
my $obj = $self->parse_script(code => $opt{code});
$self->{right_brackets}
&& $self->fatal_error(
code => $_,
pos => $p - 1,
error => "unbalanced right bracket",
);
return $obj;
}
return;
}
sub parse_lookup {
my ($self, %opt) = @_;
local *_ = $opt{code};
if (/\G\{/gc) {
my $p = pos($_);
local $self->{curly_brackets} = 1;
my $obj = $self->parse_script(code => $opt{code});
$self->{curly_brackets}
&& $self->fatal_error(
code => $_,
pos => $p - 1,
error => "unbalanced curly bracket",
);
return $obj;
}
return;
}
sub parse_block {
my ($self, %opt) = @_;
local *_ = $opt{code};
if (/\G\{/gc) {
my $p = pos($_);
local $self->{curly_brackets} = 1;
my $class_name = $self->{class};
if ($opt{is_module}) {
$class_name = $opt{prev_class};
}
my $ref = $self->{vars}{$class_name} //= [];
my $count = scalar(@{$self->{vars}{$class_name}});
unshift @{$self->{ref_vars_refs}{$class_name}}, @{$ref};
unshift @{$self->{vars}{$class_name}}, [];
$self->{vars}{$class_name} = $self->{vars}{$class_name}[0];
my $block = bless({}, 'Sidef::Types::Block::BlockInit');
# Parse whitespace (if any)
$self->parse_whitespace(code => $opt{code});
my $has_vars;
my $var_objs = [];
if (($opt{topic_var} || $opt{with_vars}) && /\G(?=\|)/) {
$has_vars = 1;
$var_objs = $self->parse_init_vars(
params => 1,
code => $opt{code},
type => 'var',
);
}
# Special '_' variable
if ($opt{topic_var} and not $has_vars) {
my $code = '_';
$has_vars = 1;
$var_objs = $self->parse_init_vars(code => \$code, type => 'var');
}
local $self->{current_block} = $block if $has_vars;
my $obj = $self->parse_script(code => $opt{code});
$self->{curly_brackets}
&& $self->fatal_error(
code => $_,
pos => $p - 1,
error => "unbalanced curly bracket",
);
#$block->{vars} = [
# map { $_->{obj} }
# grep { ref($_) eq 'HASH' and ref($_->{obj}) eq 'Sidef::Variable::Variable' } @{$self->{vars}{$class_name}}
#];
if ($has_vars) {
$block->{init_vars} = bless({vars => $var_objs}, 'Sidef::Variable::Init');
}
$block->{code} = $obj;
splice @{$self->{ref_vars_refs}{$class_name}}, 0, $count;
$self->{vars}{$class_name} = $ref;
return $block;
}
return;
}
sub append_method {
my ($self, %opt) = @_;
# Hyper-operator
if (exists $self->{hyper_ops}{$opt{op_type}}) {
push @{$opt{array}},
{
method => $self->{hyper_ops}{$opt{op_type}}[1],
arg => [$opt{method}],
};
}
# Basic operator/method
else {
push @{$opt{array}}, {method => $opt{method}};
}
# Append the argument (if any)
if (exists($opt{arg}) and (%{$opt{arg}} || ($opt{method} =~ /^$self->{operators_re}\z/))) {
push @{$opt{array}[-1]{arg}}, $opt{arg};
}
}
sub parse_methods {
my ($self, %opt) = @_;
my @methods;
local *_ = $opt{code};
my $orig_pos = pos($_);
{
if ((/\G(?![-=]>)/ && /\G(?=$self->{operators_re})/o) || /\G\./gc) {
my ($method, $req_arg, $op_type) = $self->get_method_name(code => $opt{code});
if (defined($method)) {
my $has_arg;
if (/\G\h*(?=[({])/gc || $req_arg) {
my $arg = (
$req_arg ? $self->parse_obj(code => $opt{code}, multiline => 1)
: /\G(?=\()/ ? $self->parse_arg(code => $opt{code})
: /\G(?=\{)/ ? $self->parse_block(code => $opt{code}, topic_var => 1)
: die "[PARSER ERROR] Something is wrong in the if condition"
);
if (defined $arg) {
$has_arg = 1;
$self->append_method(
array => \@methods,
method => $method,
arg => $arg,
op_type => $op_type,
);
}
else {
$self->fatal_error(
code => $_,
pos => $orig_pos,
error => "operator `$method` requires a right-side operand",
);
}
}
$has_arg || do {
$self->append_method(
array => \@methods,
method => $method,
op_type => $op_type,
);
};
redo;
}
}
}
return \@methods;
}
sub parse_suffixes {
my ($self, %opt) = @_;
my $struct = $opt{struct};
local *_ = $opt{code};
my $parsed = 0;
if (/\G(?=[\{\[])/) {
#<<<
$struct->{$self->{class}}[-1]{self} = {
$self->{class} => [
{
self => $struct->{$self->{class}}[-1]{self},
exists($struct->{$self->{class}}[-1]{call})
? (call => delete $struct->{$self->{class}}[-1]{call})
: (),
exists($struct->{$self->{class}}[-1]{ind})
? (ind => delete $struct->{$self->{class}}[-1]{ind})
: (),
}
]
};
#>>>
}
{
if (/\G(?=\{)/) {
while (/\G(?=\{)/) {
my $lookup = $self->parse_lookup(code => $opt{code});
push @{$struct->{$self->{class}}[-1]{ind}}, {hash => $lookup->{$self->{class}}};
}
$parsed ||= 1;
redo;
}
if (/\G(?=\[)/) {
while (/\G(?=\[)/) {
my ($ind) = $self->parse_expr(code => $opt{code});
push @{$struct->{$self->{class}}[-1]{ind}}, {array => $ind};
}
$parsed ||= 1;
redo;
}
if (/\G\h*(?=\()/gc) {
#<<<
$struct->{$self->{class}}[-1]{self} = {
$self->{class} => [
{
self => $struct->{$self->{class}}[-1]{self},
exists($struct->{$self->{class}}[-1]{call})
? (call => delete $struct->{$self->{class}}[-1]{call})
: (),
exists($struct->{$self->{class}}[-1]{ind})
? (ind => delete $struct->{$self->{class}}[-1]{ind})
: (),
}
]
};
#>>>
my $arg = $self->parse_arg(code => $opt{code});
push @{$struct->{$self->{class}}[-1]{call}},
{
method => 'call',
(%{$arg} ? (arg => [$arg]) : ())
};
redo;
}
}
$parsed;
}
sub backtrack_whitespace {
my ($self, %opt) = @_;
local *_ = $opt{code};
# Backtrack the removal of whitespace
while (1) {
my $s = substr($_, pos($_) - 1, 1);
if ($s =~ /\R/) {
$self->{line} -= 1;
pos($_) -= 1;
last;
}
elsif ($s =~ /\h/) {
pos($_) -= 1;
}
else {
last;
}
}
}
sub parse_obj {
my ($self, %opt) = @_;
my %struct;
local *_ = $opt{code};
if (not($opt{multiline}) and /\G\h*(?=\R)/gc) {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "unexpected end-of-statement",
);
}
my ($obj, $obj_key, $method) = $self->parse_expr(code => $opt{code});
if (defined $obj) {
push @{$struct{$self->{class}}}, {self => $obj};
# for var in array { ... }
if (ref($obj) eq 'Sidef::Types::Block::For' and /\G\h*(?=[*:]?$self->{var_name_re})/goc) {
my $class_name = $self->{class};
my @loops;
{
my @vars;
while (/\G([*:])?($self->{var_name_re})/gc) {
my $type = $1;
my $name = $2;
push @vars,
bless(
{
name => $name,
type => 'var',
class => $class_name,
(
$type
? (
slurpy => 1,
($type eq '*' ? (array => 1) : (hash => 1)),
)
: ()
),
},
'Sidef::Variable::Variable'
);
unshift @{$self->{vars}{$class_name}},
{
obj => $vars[-1],
name => $name,
count => 1,
type => 'var',
line => $self->{line},
};
$type && last;
/\G\h*,\h*/gc || last;
}
/\G\h*(?:in|∈|=|)\h*/gc
|| $self->fatal_error(
error => "expected the token <<in>> after variable declaration in for-loop",
code => $_,
pos => pos($_),
);
my $expr = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
push @loops,
{
vars => \@vars,
expr => $expr,
};
/\G\h*,\h*/gc && redo;
}
my $block = (
/\G\h*(?=\{)/gc
? $self->parse_block(code => $opt{code})
: $self->fatal_error(
error => "expected a block",
code => $_,
pos => pos($_),
)
);
# Remove the for-loop variables from the outer scope
#<<<
my %loop_vars = map {
map { refaddr($_) => 1 } @{$_->{vars}}
} @loops;
@{$self->{vars}{$class_name}} = grep {
ref($_) ne 'HASH'
or not exists $loop_vars{refaddr($_->{obj})}
} @{$self->{vars}{$class_name}};
#>>>
# Store the info
$obj->{block} = $block;
$obj->{loops} = \@loops;
# Re-bless the $obj into a different class
bless $obj, 'Sidef::Types::Block::ForIn';
}
elsif ($obj_key) {
my $arg = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
if (defined $arg) {
my @arg = ($arg);
if (ref($obj) eq 'Sidef::Types::Block::For') {
if ($#{$arg->{$self->{class}}} == 2) {
@arg = (
map {
{ $self->{class} => [$_] }
} @{$arg->{$self->{class}}}
);
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
$obj->{expr} = \@arg;
$obj->{block} = $block;
bless $obj, 'Sidef::Types::Block::CFor';
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `for` loop",
reason => "expected a block after `for(;;)`",
);
}
}
elsif ($#{$arg->{$self->{class}}} == 0) {
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code}, topic_var => 1);
$obj->{expr} = $arg;
$obj->{block} = $block;
bless $obj, 'Sidef::Types::Block::ForEach';
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `for` loop",
reason => "expected a block after `for(...)`",
);
}
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `for` loop: incorrect number of arguments",
);
}
}
elsif (ref($obj) eq 'Sidef::Types::Block::ForEach') {
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code}, topic_var => 1);
$obj->{expr} = $arg;
$obj->{block} = $block;
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `foreach` loop",
reason => "expected a block after `foreach(...)`",
);
}
}
elsif (ref($obj) eq 'Sidef::Types::Block::If') {
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code}, with_vars => 1);
push @{$obj->{if}}, {expr => $arg, block => $block};
ELSIF: {
$self->parse_whitespace(code => $opt{code});
if (/\Gelsif\h*(?=\()/gc) {
my $arg = $self->parse_arg(code => $opt{code});
$self->parse_whitespace(code => $opt{code});
my $block = $self->parse_block(code => $opt{code}, with_vars => 1) // $self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `if` statement",
reason => "expected a block after `elsif(...)`",
);
push @{$obj->{if}}, {expr => $arg, block => $block};
redo ELSIF;
}
}
if (/\Gelse\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
$obj->{else}{block} = $block;
}
$self->backtrack_whitespace(code => $opt{code});
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `if` statement",
reason => "expected a block after `if(...)`",
);
}
}
elsif (ref($obj) eq 'Sidef::Types::Block::With') {
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code}, topic_var => 1);
push @{$obj->{with}}, {expr => $arg, block => $block};
ORWITH: {
$self->parse_whitespace(code => $opt{code});
if (/\Gorwith\h*(?=\()/gc) {
my $arg = $self->parse_arg(code => $opt{code});
$self->parse_whitespace(code => $opt{code});
my $block = $self->parse_block(code => $opt{code}, topic_var => 1) // $self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `with` statement",
reason => "expected a block after `orwith(...)`",
);
push @{$obj->{with}}, {expr => $arg, block => $block};
redo ORWITH;
}
}
if (/\Gelse\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code});
$obj->{else}{block} = $block;
}
$self->backtrack_whitespace(code => $opt{code});
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `with` statement",
reason => "expected a block after `with(...)`",
);
}
}
elsif (ref($obj) eq 'Sidef::Types::Block::While') {
if (/\G\h*(?=\{)/gc) {
my $block = $self->parse_block(code => $opt{code}, with_vars => 1);
$obj->{expr} = $arg;
$obj->{block} = $block;
}
else {
$self->fatal_error(
code => $_,
pos => pos($_) - 1,
error => "invalid declaration of the `while` statement",
reason => "expected a block after `while(...)`",
);
}
}
else {
push @{$struct{$self->{class}}[-1]{call}}, {method => $method, arg => \@arg};
}
}
else {
$self->fatal_error(
code => $_,
error => "expected an argument. Did you mean '$method()' instead?",
pos => pos($_) - 1,
);
}
}
{
# Method call
if (/\G\h*(?=\.\h*(?:$self->{method_name_re}|[(\$]))/ogc) {
my $methods = $self->parse_methods(code => $opt{code});
push @{$struct{$self->{class}}[-1]{call}}, @{$methods};
}
# Code extended on a newline
if (/\G\h*\\(?!\\)/gc) {
$self->parse_whitespace(code => $opt{code});
redo;
}
# Object call
if (/\G\h*(?=\()/gc) {
my $arg = $self->parse_arg(code => $opt{code});
push @{$struct{$self->{class}}[-1]{call}},
{
method => 'call',
(%{$arg} ? (arg => [$arg]) : ())
};
redo;
}
# Do-while construct
if (ref($obj) eq 'Sidef::Types::Block::Do' and /\G\h*while\b/gc) {
my $arg = $self->parse_obj(code => $opt{code});
push @{$struct{$self->{class}}[-1]{call}}, {keyword => 'while', arg => [$arg]};
}
# Parse array and hash fetchers ([...] and {...})
if (/\G\.\h*(?=[\[\{])/gc or 1) {
$self->parse_suffixes(code => $opt{code}, struct => \%struct) && redo;
}
# Tightly-binded operator
if (
/\G(?![=-]>)/ # not '=>' or '->'
&& (
/\G(?=$self->{operators_re})/o # operator
|| /\G\h*\.\h*(?!\.\.)(?=$self->{operators_re})/gco # dot followed by operator
|| /\G(?=[⁰¹²³⁴⁵⁶⁷⁸⁹])/ # unicode superscript
)
) {
my $orig_pos = pos($_);
my ($method, $req_arg, $op_type) = $self->get_method_name(code => $opt{code});
if (defined($method)) {
my $has_arg;
if ($req_arg) {
my $arg = $self->parse_obj(code => $opt{code}, multiline => 1);
if (defined $arg) {
if (ref $arg ne 'HASH') {
$arg = {$self->{class} => [{self => $arg}]};
}
my $methods = $self->parse_methods(code => $opt{code});
if (@{$methods}) {
push @{$arg->{$self->{class}}[-1]{call}}, @{$methods};
}
$has_arg = 1;
$self->append_method(
array => \@{$struct{$self->{class}}[-1]{call}},
method => $method,
arg => $arg,
op_type => $op_type,
);
}
else {
$self->fatal_error(
code => $_,
pos => $orig_pos,
error => "operator `$method` requires a right-side operand",
);
}
}
$has_arg || do {
$self->append_method(
array => \@{$struct{$self->{class}}[-1]{call}},
method => $method,
op_type => $op_type,
);
};
redo;
}
}
}
}
else {
return;
}
return \%struct;
}
sub parse_script {
my ($self, %opt) = @_;
my %struct;
local *_ = $opt{code};
MAIN: {
$self->parse_whitespace(code => $opt{code});
if (/\G\@:([^\W\d]\w*+)/gc) {
push @{$struct{$self->{class}}}, {self => bless({name => $1}, 'Sidef::Variable::Label')};
redo;
}
if (/\G(?:[;,]+|=>)/gc) {
redo;
}
my $obj;
# Ternary operator
if (%struct && /\G\?/gc) {
$self->parse_whitespace(code => $opt{code});
my $true = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
$self->parse_whitespace(code => $opt{code});
/\G:/gc
|| $self->fatal_error(
code => $_,
pos => pos($_),
error => "invalid usage of the ternary operator",
reason => "expected ':'",
);
$self->parse_whitespace(code => $opt{code});
my $false = (
/\G(?=\()/
? $self->parse_arg(code => $opt{code})
: $self->parse_obj(code => $opt{code})
);
$obj = bless(
{
cond => scalar {$self->{class} => [pop @{$struct{$self->{class}}}]},
true => $true,
false => $false
},
'Sidef::Types::Bool::Ternary'
);
}
else {
$obj = $self->parse_obj(code => $opt{code});
}
if (defined $obj) {
push @{$struct{$self->{class}}}, {self => $obj};
{
my $pos_before = pos($_);
$self->parse_whitespace(code => $opt{code});
# End of expression
if (/\G(?:[;,]+|=>)/gc or substr($_, $pos_before, pos($_) - $pos_before) =~ /\R/) {
redo MAIN;
}
# Code extended on a newline
if (/\G\\(?!\\)/gc) {
$self->parse_whitespace(code => $opt{code});
}
my $is_operator = /\G(?!->)/ && /\G(?=($self->{operators_re}))/o;
if ($is_operator or /\G(?:->|\.)\h*/gc) {
# Implicit end of statement -- redo
$self->parse_whitespace(code => $opt{code});
my $methods;
if ($is_operator) {
$methods = $self->parse_methods(code => $opt{code});
}
else {
my $code = substr($_, pos);
my $dot_op = $code =~ /^\./;
if ($dot_op) { $code = ". $code" }
else { $code = ".$code" }
$methods = $self->parse_methods(code => \$code);
pos($_) += pos($code) - ($dot_op ? 2 : 1);
}
if (@{$methods}) {
push @{$struct{$self->{class}}[-1]{call}}, @{$methods};
}
else {
$self->fatal_error(
error => 'incomplete method name',
code => $_,
pos => pos($_) - 1,
);
}
$self->parse_suffixes(code => $opt{code}, struct => \%struct);
redo;
}
elsif (/\G(if|while|and|or)\b\h*/gc) {
my $keyword = $1;
my $obj = $self->parse_obj(code => $opt{code});
push @{$struct{$self->{class}}[-1]{call}}, {keyword => $keyword, arg => [$obj]};
redo;
}
else {
redo MAIN;
}
}
}
if (/\G(?:[;,]+|=>)/gc) {
redo;
}
# We are at the end of the script.
# We make some checks, and return the \%struct hash ref.
if (/\G\z/) {
$self->check_declarations($self->{ref_vars});
return \%struct;
}
if (/\G\]/gc) {
if (--$self->{right_brackets} < 0) {
$self->fatal_error(
error => 'unbalanced right bracket',
code => $_,
pos => pos($_) - 1,
);
}
return \%struct;
}
if (/\G\}/gc) {
if (--$self->{curly_brackets} < 0) {
$self->fatal_error(
error => 'unbalanced curly bracket',
code => $_,
pos => pos($_) - 1,
);
}
return \%struct;
}
# The end of an argument expression
if (/\G\)/gc) {
if (--$self->{parentheses} < 0) {
$self->fatal_error(
error => 'unbalanced parenthesis',
code => $_,
pos => pos($_) - 1,
);
}
return \%struct;
}
$self->fatal_error(
code => $_,
pos => (pos($_)),
error => "expected a method",
);
pos($_) += 1;
redo;
}
}
};
1