#!/usr/bin/env perl

use utf8;
use 5.016;

BEGIN {    # support for running sidef locally from everywhere
    if (-w __FILE__) {
        require File::Spec;
        require File::Basename;
        unshift @INC,
          File::Spec->catdir(
                             File::Basename::dirname(
                                                       File::Spec->file_name_is_absolute(__FILE__)
                                                     ? __FILE__
                                                     : File::Spec->rel2abs(__FILE__)
                                                    ),
                             File::Spec->updir,
                             'lib'
                            );
    }
}

binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8" if $^P == 0;    # to work under Devel::* modules

use Sidef;

my $name    = 'Sidef';
my $version = $Sidef::VERSION;

my %args;
if ($#ARGV != -1 and chr ord $ARGV[0] eq '-') {
    require Getopt::Std;
    $Getopt::Std::STANDARD_HELP_VERSION = 1;
    Getopt::Std::getopts('e:E:Dho:ivHWwbcrR:tCO:kP:M:sN:', \%args);
}

# Fix potential case mismatches for -R
if (defined $args{R}) {
    if (lc($args{R}) eq 'perl') {
        $args{R} = 'Perl';
    }
    elsif (lc($args{R}) eq 'sidef') {
        $args{R} = 'Sidef';
    }
}

# Help
if (defined $args{h}) {
    HELP_MESSAGE();
    exit 0;
}

# Version
if (defined $args{v}) {
    VERSION_MESSAGE();
    exit 0;
}

# Warnings stack backtrace
if (defined $args{w}) {
    $SIG{__WARN__} = sub {
        require Carp;
        Carp::cluck(@_);
    };
}

# Fatal warnings stack backtrace
if (defined $args{W}) {
    $SIG{__DIE__} = $SIG{__WARN__} = sub {
        require Carp;
        Carp::confess(@_);
    };
}

# Interactive help
if (defined $args{H}) {
    help_interactive();
    exit 0;
}

# Interactive coding
if (defined $args{i}) {
    code_interactive();
    exit 0;
}

# Precision
if (defined $args{P}) {
    require Sidef::Types::Number::Number;
    if ($args{P} <= 0) {
        die "Invalid precision: <<$args{P}>> (expected a positive integer)\n";
    }
    $Sidef::Types::Number::Number::PREC = $args{P} << 2;
}

# Other Number options
if (defined $args{N}) {
    require Sidef::Types::Number::Number;
    my @options = split(/\s*;\s*/, $args{N});
    foreach my $option (@options) {
        if ($option =~ /^\s*(\w+)\s*=\s*(\S+)/) {
            my ($name, $value) = ($1, $2);

            if ($value eq 'true') {
                $value = 1;
            }
            elsif ($value eq 'false') {
                $value = 0;
            }

            no strict 'refs';
            ${'Sidef::Types::Number::Number::' . $name} = $value;
        }
        else {
            die "Invalid format: <<$option>>!\nExpected: 'NAME1=VALUE1; NAME2=VALUE2;'";
        }
    }
}

# Test mode
if (defined $args{t}) {

    local $args{c} = 0;
    my @argv = splice(@ARGV);

    my @fails;
    require Encode;
    while (defined(my $script_name = shift @argv)) {

        my $script_name = Encode::decode_utf8($script_name);

        say "\n** Executing: $script_name";
        say "-" x 80;

        my $sidef = Sidef->new(opt  => \%args,
                               name => $script_name);

        my $code     = read_script($script_name);
        my $deparsed = eval { $sidef->compile_code($code, 'Perl') };

        my $slept = 0;
        if ($@) {
            warn "[ERROR] Can't parse the script `$script_name`: $@";
            push @fails, $script_name;
            sleep 2;
            $slept = 1;
        }
        else {
            local $SIG{INT} = sub {
                die "Stopped by user...";
            };

            if (defined $args{C}) {
                say "$script_name syntax OK";
                next;
            }

            $sidef->execute_perl($deparsed);
        }

        if (not($slept) and $@) {
            warn "[ERROR] Error encountered on script `$script_name`: $@";
            push @fails, $script_name;
            sleep(2) if @argv;
        }
    }

    if (@fails) {
        say "\n";
        say "-" x 80;
        say ":: The following scripts failed";
        say "-" x 80;
        say "$_" for @fails;
    }
}

# Default
else {
    my $script_name = '-';

    $args{E} = $args{e} if exists($args{e});

    my $code = exists($args{E})
      ? do {
        defined($args{E}) || die "No code specified for -E.\n";
        $script_name = '-E';
        require Encode;
        Encode::decode_utf8($args{E});
      }
      : defined($ARGV[0]) ? do {
        $script_name = shift @ARGV;
        if ($script_name eq '-') {
            local $/;
            <STDIN>;
        }
        else {
            read_script($script_name);
        }
      }
      : (-t STDIN) ? do { code_interactive(); exit 0; }
      :              do { local $/;           <STDIN> };

    $code // exit 2;

    my $sidef = Sidef->new(opt  => \%args,
                           name => $script_name);

    # Dump the AST
    if (defined $args{D}) {
        dump_ast($sidef->parse_code($code));
    }

    # Deparse code
    elsif (defined($args{r}) or defined($args{R})) {
        my $deparsed = $sidef->compile_code($code, $args{R});

        if (defined($args{R}) and $args{R} eq 'Perl') {

            require File::Basename;

            my $header =
                "\nuse lib (" . q{"}
              . quotemeta(File::Basename::dirname($INC{"Sidef.pm"})) . q{"}
              . ");\n\n"
              . "use Sidef;\n\n"
              . "binmode(STDIN, ':utf8');\n"
              . "binmode(STDOUT, ':utf8');\n"
              . "binmode(STDERR, ':utf8') if \$^P == 0;\n";

            $deparsed = $header . $deparsed;
        }

        output($deparsed);
    }

    # Compile the code to a Perl program
    elsif (defined $args{c}) {
        compile_to_perl(code => $sidef->compile_code($code, 'Perl'));
    }

    # Check the syntax
    elsif (defined $args{C}) {
        eval { $sidef->parse_code($code) };
        die $@ if $@;
        say "$script_name syntax OK";
    }

    # Execute the code
    else {
        $sidef->execute_code($code);
        die $@ if $@;
    }
}

#
## Subroutines
#

sub HELP_MESSAGE {
#<<<
    my %switches = (
                    '-i file'    => 'execute a program in interactive mode',
                    '-c'         => 'compile the code into a Perl program',
                    '-C'         => 'check syntax only',
                    '-D'         => 'dump the syntax tree of a program',
                    '-o file'    => 'file where to dump the output',
                    '-O level'   => ['perform code optimizations before execution',
                                     'valid levels: [0], 1, 2'],
                    '-P int'     => 'set the precision of floating-point numbers (default: ' . int($Sidef::Types::Number::Number::PREC / 4) . ')',
                    '-M mode'    => ['set the rounding mode of floating-point numbers',
                                     'valid modes: [near], zero, inf, +inf, -inf, faith'],
                    '-N options' => ['modify class-variables inside the Number class',
                                     "valid format: 'VERBOSE=1; USE_YAFU=1; USE_PRIMECOUNT=1'"],
                    '-k'         => 'keep track of potentially incorrect parser interpretations',
                    '-E program' => 'one line of program',
                    '-H'         => 'interactive help',
                    '-s'         => 'save compiled code in a database to reduce boot-time',
                    '-v'         => 'print version number and exit',
                    '-t'         => 'treat all command-line arguments as scripts',
                    '-r'         => 'parse and deparse a Sidef program',
                    '-R lang'    => ['parse and deparse a Sidef program into a given language',
                                     'valid values: sidef, perl'],
                    '-w'         => 'enable warnings with stack backtrace',
                    '-W'         => 'make warnings fatal (with stack backtrace)',
    );
#>>>
    require File::Basename;
    my $basename = File::Basename::basename($0);

    print <<"USAGE";

Usage: $basename [switches] [--] [programfile] [arguments]

USAGE

    require List::Util;
    my $max_width = List::Util::max(map { length } keys %switches);
    $max_width += 4;

    foreach my $key (sort { lc($a) cmp lc($b) or lc($b) cmp lc($a) or $b cmp $a } keys %switches) {
        if (ref $switches{$key} eq 'ARRAY') {
            printf "  %-${max_width}s%s\n", $key, $switches{$key}[0];
            foreach my $i (1 .. $#{$switches{$key}}) {
                printf "  %-${max_width}s%s\n", '', $switches{$key}[$i];
            }
        }
        else {
            printf "  %-${max_width}s%s\n", $key, $switches{$key};
        }
    }

    print <<"END";

Run '$basename' for entering the interactive mode.

END
}

sub VERSION_MESSAGE {
    print "$name $version\n";
}

sub read_script {
    my ($script_name) = @_;
    open my $fh, '<:utf8', $script_name
      or die qq{Can't open sidef script "$script_name": $!\n};
    local $/;
    <$fh>;
}

sub help_interactive {
    my ($term) = @_;

    require File::Spec;
    require File::Basename;

    require Encode;
    require Term::ReadLine;

    $term //= Term::ReadLine->new("$name $version -- help interactive mode");

    print <<"HELP";
Welcome to $name $version! This is the interactive help utility.

Enter the name of any object, keyword, or topic to get help on writing
$name programs and using $name modules.  To quit this help utility, just
type "quit".

HELP

    my $sidef = Sidef->new(
                           name       => '-H',
                           opt        => {i           => 1, %args},
                           parser_opt => {interactive => 1},
                          );

    {
        my $line = Encode::decode_utf8(
            $term->readline('help> ')
              // do { print "\n"; return }
        );

        my $ccode = eval { $sidef->compile_code($line, 'Perl') };

        if ($@) {

            # Valid keywords for 'exit'
            if ($line eq 'quit' or $line eq 'q' or $line eq 'exit') {
                return;
            }

            # Otherwise, a syntax error
            warn $@;
            redo;
        }

        my @refs = (map { ref($_) } $sidef->execute_perl($ccode));

        foreach my $ref (@refs) {
            $ref eq '' && do { warn "Not an object!\n"; next };
            my $name = $ref =~ s{::}{/}gr;
            my $file = $INC{$name . '.pm'};
            my $pod;
            foreach my $dir (@INC) {
                if (-e (my $f = File::Spec->catfile($dir, $name . '.pod'))) {
                    $pod = $f;
                    last;
                }
            }
            if (defined($pod)) {
                system 'perldoc', $pod;
                $? && system 'man', $ref;
            }
            else {
                system 'man', $ref;
                $? && system 'perldoc', $ref;
            }
        }

        redo;
    }
}

sub create_completion_tree {
    scalar {
            table       => {},
            special_key => "\0",
           };
}

sub add_tree_entry {
    my ($tree, $key, $value) = @_;

    my $ref = $tree->{table};

    foreach my $item (@$key) {
        $ref = $ref->{$item} //= {};
        undef $ref->{$tree->{special_key}}{$value};
    }

    $tree;
}

sub search_tree {
    my ($tree, $prefix) = @_;

    my $ref = $tree->{table};

    foreach my $item (@$prefix) {
        if (exists $ref->{$item}) {
            $ref = $ref->{$item};
        }
        else {
            return;
        }
    }

    sort keys %{$ref->{$tree->{special_key}} // {}};
}

sub add_class_methods_to_completion {
    my ($tree) = @_;

    my $modules_count = scalar(keys %INC);

    state %seen;
    state $included_modules = $modules_count - 1;

    if ($modules_count == $included_modules) {
        return 1;
    }

    foreach my $module (keys %INC) {

        next if $seen{$module}++;

        my $class = $module =~ s{\.pm\z}{}r =~ s{\W+}{::}gr;
        $class =~ /^Sidef::Types::/ or next;

        foreach my $method_name (keys %{(eval { $class->methods }) // {}}) {
            add_tree_entry($tree, [split(//, $method_name)], $method_name);
        }
    }

    $included_modules = $modules_count;
    return 1;
}

sub add_words_to_completion {
    my ($tree, $string) = @_;

    while ($string =~ /(\w+)/g) {
        my $word = $1;
        if (length($word) <= 50) {
            add_tree_entry($tree, [split(//, $word)], $word);
        }
    }

    return 1;
}

sub code_interactive {

    require Encode;
    require File::Spec;
    require Term::ReadLine;

    my $term = Term::ReadLine->new("$name $version -- interactive mode");

    my $sidef;
    my $init_sidef = sub {
        $sidef = Sidef->new(
                            name       => '-i',
                            opt        => {i           => 1, %args},
                            parser_opt => {interactive => 1},
                           );
        $sidef->execute_code('');    # warm-up
    };

    $init_sidef->();

    my ($copy_array, $copy_hash);

    $copy_array = sub {
        my ($array) = @_;

        my @copy;
        foreach my $item (@$array) {
            if (ref($item) eq 'ARRAY') {
                push @copy, __SUB__->($item);
            }
            elsif (ref($item) eq 'HASH') {
                push @copy, $copy_hash->($item);
            }
            else {
                push @copy, $item;
            }
        }

        \@copy;
    };

    $copy_hash = sub {
        my ($hash) = @_;

        my %copy;
        foreach my $key (keys %$hash) {
            my $value = $hash->{$key};

            if (ref($value) eq 'ARRAY') {
                $copy{$key} = $copy_array->($value);
            }
            elsif (ref($value) eq 'HASH') {
                $copy{$key} = __SUB__->($value);
            }
            else {
                $copy{$key} = $value;
            }
        }

        \%copy;
    };

    require Time::HiRes;

    print <<"EOT" if 0;
            **   **         ****   *           *********   *********
          * * ** * *        ****   **          ** ** **    ** ** **
           **   **          ****   ***         *********   *  *  *
  **        **        **    ****   *  *        ******      ******
* * *     * * *     * * *   ****   ** **       ** **       ** **
 **        **        **     ****   ******      ******      *  *
       **   **              ****   *  *  *     *********   ***
     * * ** * *             ****   ** ** **    ** ** **    **
      **   **               ****   *********   *********   *
EOT

    print <<"EOT";
Sidef $version, running on \u$^O, using Perl $^V.
Type "help", "copyright" or "license" for more information.
EOT

    my $valid_lines = '';
    my ($vars, $ref_vars_refs);

    my $completion_tree;
    my $history_support = $term->can('ReadHistory') && $term->can('Attribs');
    my $history_file    = File::Spec->catfile($sidef->get_sidef_config_dir(), 'sidef_history.txt');

    if ($history_support) {

        if (not -e $history_file) {
            open my $fh, '>', $history_file;
        }

        $completion_tree = create_completion_tree();

        my $attr = $term->Attribs;

        $attr->{basic_quote_characters} = q{};

        add_class_methods_to_completion($completion_tree);

        my @results;
        $attr->{completion_entry_function} = sub {
            my ($prefix, $state) = @_;

            my $root = '';
            if ($prefix !~ /^\w+\z/ and $prefix =~ /^(.*)\b(\w+)\z/) {
                $root   = $1;
                $prefix = $2;
            }

            if ($state == 0) {
                @results = search_tree($completion_tree, [split(//, $prefix)]);
            }

            @results || return undef;
            $root . shift(@results);
        };

        $term->ReadHistory($history_file);
    }

    my $tΔ = 0;

    my @values;
    my $FH = undef;

    if (@ARGV) {
        my $file = shift(@ARGV);
        open $FH, '<:utf8', $file
          or die "Can't open file <<$file>> for reading: $!\n";
    }

  MAINLOOP: {
        my $line = '';

      LINE: {

            if (defined($FH) and !eof($FH)) {
                chomp(my $curr_line = <$FH>);
                if ($line eq '' and $curr_line =~ /^\s*__(?:END|DATA)__\s*\z/) {
                    $curr_line .= "\n" . do { local $/; <$FH> };
                }
                if ($history_support and $curr_line ne '' and $line eq '') {
                    $term->addhistory($curr_line =~ s/\R/\r/gr);
                }
                $line .= $curr_line;
            }
            else {
                $line .= Encode::decode_utf8($term->readline($line eq '' ? '> ' : '  ') // return);
            }

            if ($line eq 'help') {
                help_interactive($term);
                redo MAINLOOP;
            }
            elsif ($line eq '##') {
                say "  ***   last result computed in $tΔ seconds";
                redo MAINLOOP;
            }
            elsif ($line =~ /^#+\h*load\h+(.+)/) {
                my $file = unpack('A*', $1);
                open $FH, '<:utf8', $file or do {
                    warn "Can't open file <<$file>> for reading: $!\n";
                    redo MAINLOOP;
                };
                redo MAINLOOP;
            }
            elsif ($line =~ /^#+\h*exec\h+(.+)/) {
                my $file = unpack('A*', $1);
                $init_sidef->();
                open my $fh, '<:utf8', $file or do {
                    warn "Can't open file <<$file>> for reading: $!\n";
                    redo MAINLOOP;
                };
                $line = do { local $/; <$fh> };
                close $fh;
            }
            elsif ($line =~ /^#+\h*save\h+(.+)/) {
                my $file = unpack('A*', $1);
                open my $fh, '>:utf8', $file or do {
                    warn "Can't open file <<$file>> for writing: $!\n";
                    redo MAINLOOP;
                };
                print $fh $valid_lines;
                close $fh;
                say "** Created file: $file";
            }
            elsif ($line eq 'copyright') {
                print <<'EOT';
Copyright © 2013-2024 Daniel Șuteu, Ioana Fălcușan
All Rights Reserved.
EOT
                redo MAINLOOP;
            }
            elsif ($line eq 'license') {
                print <<'EOT';

This program is free software; you can redistribute it
and/or modify it under the terms of the Artistic License (2.0).
For more details, see the full text in the LICENSE file.

This program is distributed in the hope that it will be
useful, but without any warranty; without even the implied
warranty of merchantability or fitness for a particular purpose.

For more information, see:
    https://github.com/trizen/sidef
    https://www.perlfoundation.org/artistic-license-20.html

EOT
                redo MAINLOOP;
            }
        }

        # Replace top-level variables and constants with globals
        if (not defined($args{r}) and not defined($args{R})) {
            $line =~ s/^\h*(?:var|define|const|static)\b/global/;
        }

        $vars          = $copy_hash->($sidef->{parser}{vars});
        $ref_vars_refs = $copy_hash->($sidef->{parser}{ref_vars_refs});

        $line =~ s{#(-?[1-9][0-9]*)\b}{(abs($1) <= scalar(@values)) ? ('(' . $values[($1 < 0) ? $1 : $1-1]->{value} . ')') : "#$1"}ge;

        # Last character was '\': read the next line
        if ($line =~ /\\\s*\z/) {
            $line .= "\n";
            goto LINE;
        }

        my $ccode = eval { $sidef->compile_code($line, $args{r} ? 'Sidef' : ($args{R} || 'Perl')) };

        if ($@) {

            # Valid keywords for 'exit'
            if ($line eq 'q' or $line eq 'exit' or $line eq 'quit') {
                return;
            }

            # Reset the parser
            if ($line eq 'reset') {
                $init_sidef->();
                undef $vars;
                undef $ref_vars_refs;
                @values = ();
                redo;
            }

            # Restore parser variables
            if (defined($vars) and defined($ref_vars_refs)) {
                %{$sidef->{parser}{vars}}          = %$vars;
                %{$sidef->{parser}{ref_vars_refs}} = %$ref_vars_refs;
            }

            # Give up if the previous line is blank,
            # or when it's impossible to recover from an error
            if (
                   $@ =~ /is not declared in the current scope/i
                or $@ =~ /invalid \S+ declaration/i
                or $@ =~ /attempt to (?:use|call|delete) /i
                or $@ =~ /not declared in the current scope/i
                or $@ =~ /expected a block after/i
                or $@ =~ /unexpected end-of-statement/i
                or (
                      $@    =~ /unbalanced|string terminator|delimiter/
                    ? $line =~ /\R\R\z/
                    : $line =~ /\R\z/
                   )
              ) {
                warn $@;
                redo;
            }

            $line .= "\n";
            goto LINE;
        }
        else {
            $valid_lines .= "$line\n";    # store valid lines
        }

        if ($history_support) {
            if ($line =~ /\R/) {
                $term->addhistory($line =~ s/\R/\r/gr);
            }
            $term->append_history(1, $history_file);
        }

        if (defined($args{r}) or defined($args{R})) {
            output($ccode);
        }
        elsif ($line =~ /\S/ and not $line =~ /^\s*#.*$/) {

            my $t0 = eval { [Time::HiRes::gettimeofday()] };

            my @results = $sidef->execute_perl($ccode);

            if ($@) {
                print $@;
            }
            elsif ($history_support) {
                add_words_to_completion($completion_tree, $line);
            }

            $tΔ = eval { Time::HiRes::tv_interval($t0) };

            # use overload;
            # overload::StrVal($_) ? "$_" : $_->dump;

            my $dump = join(
                ', ',
                map {
                        (ref($_) ? UNIVERSAL::can($_, 'dump') ? $_->dump : $_ : ($_ // 'nil'))
                      . ((ref($_) eq 'Sidef::Types::Number::Number' and ref($$_) eq 'Math::MPFR' and Math::MPFR::Rmpfr_number_p($$_)) ? 'f' : '')
                  } @results
            );

            $dump = "($dump)" if @results > 1;

            push @values,
              {
                type  => ((scalar(@results) == 1) ? 'scalar' : 'list'),
                value => $dump,
              };

            say "#" . scalar(@values) . " = $dump";

            if ($history_support) {
                add_class_methods_to_completion($completion_tree);
            }
        }
        redo;
    }
}

sub _get_loaded_modules {
    my @modules;
    foreach my $key (sort { length($a) <=> length($b) || $a cmp $b } keys %INC) {
        if ($key =~ /^(Sidef\b.*)\.pm\z/) {
            push @modules, $1 =~ s{/}{::}gr;
        }
    }
    return @modules;
}

sub output {
    my ($content) = @_;

    my $out_fh = \*STDOUT;

    if (defined $args{o}) {
        open $out_fh, '>:utf8', $args{o}
          or die "Can't open file '$args{o}' for write: $!\n";
    }
    print {$out_fh} $content;

    return $out_fh;
}

sub dump_ast {
    my ($ast) = @_;

    eval { require Data::Dump };

    if ($@) {
        die qq{** "Data::Dump" is not installed!\n};
    }
    else {
        my $out_fh = output('');

        my $requirify = sub {
            join('', map { "require '" . (s{::}{/}gr) . ".pm';\n" } @_);
        };

        print {$out_fh} $requirify->(_get_loaded_modules());
        print {$out_fh} Data::Dump::pp($ast) . "\n";
    }
}

sub compile_to_perl {
    my (%opt) = @_;

    require File::Spec;
    require File::Basename;

    my $path = File::Spec->catdir(File::Basename::dirname($INC{'Sidef.pm'}), 'Sidef');

    my $package_content = <<"HEAD";
#!$^X

eval 'exec $^X  -S \$0 \${1+"\$@"}'
    if 0; # not running under some shell

use utf8;

binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8" if \$^P == 0;    # to work under Devel::* modules

my %REQ;
my %MODULE;
HEAD

    $package_content .= "BEGIN { %MODULE = (\n";

    require File::Find;
    File::Find::find(
        {
         no_chdir => 1,
         wanted   => sub {
             if (/\.pm\z/ and -f) {

                 local $/;
                 open my $fh, '<:utf8', $_
                   or die "Can't open file `$_` for reading: $!";

                 my $token   = tr/A-Za-z0-9/_/cr;
                 my $content = <$fh>;

                 if ($content =~ /^package\h+([\w:]+)/) {
                     $package_content .= qq{'${1}' => };
                 }
                 else {
                     die qq{ERROR: can't get the package name from file `$_`};
                 }

                 $package_content .= qq{<<'${token}',\n};
                 $package_content .= $content;
                 $package_content .= "\n$token\n";

                 close $fh;
             }
         }
        } => ($path, $INC{'Sidef.pm'})
    );

    $package_content .= <<'FOOT';
);

sub __load_sidef_module__ {
    my ($name) = @_;
    if (not exists $REQ{$name}) {
        my $module = $name =~ s{::}{/}gr . '.pm';
        if (exists $MODULE{$name} and not exists $INC{$module}) {

            # Load the Sidef used modules
            $MODULE{$name} =~ s{^\h*
                  use \h+ (?:
                      parent \s+ qw\((.*?)\)
                    | (Sidef::[\w:]+)
                  )
            }{
                  join(
                  ";\n" => map{
                    exists($REQ{$_})
                        ? ()
                        : "BEGIN{ main::__load_sidef_module__('${_}') }" } split(' ', $+)
                  ) . (defined($1) ? "\nuse parent qw(-norequire $1);\n" : '')
            }gxmse;

            $INC{$module} = 1;
            eval($MODULE{$name});
            die "[FATAL ERROR] Can't load `$module`: $@" if $@;
        }
        else {
            require $module;
        }
        $REQ{$name} = 1;
    }
    return 1;
}

FOOT

    my $requirify = sub {
        join('', map { "__load_sidef_module__('${_}');\n" } grep { $_ ne 'Sidef::Optimizer' } @_);
    };

    $package_content .= $requirify->(_get_loaded_modules(), 'Sidef::Module::OO', 'Sidef::Module::Func');

    my @used_pkgs;
    while ($opt{code} =~ /^use (Sidef::\S+);$/gm) {
        push @used_pkgs, $1;
    }

    $package_content .= $requirify->(@used_pkgs) if @used_pkgs;
    $package_content .= "}\n\n";

    my $out_fh = output('');
    print {$out_fh} $package_content;
    print {$out_fh} $opt{code};
}

__END__

=encoding utf8

=head1 NAME

                **   **         ****   *           *********   *********
              * * ** * *        ****   **          ** ** **    ** ** **
               **   **          ****   ***         *********   *  *  *
      **        **        **    ****   *  *        ******      ******
    * * *     * * *     * * *   ****   ** **       ** **       ** **
     **        **        **     ****   ******      ******      *  *
           **   **              ****   *  *  *     *********   ***
         * * ** * *             ****   ** ** **    ** ** **    **
          **   **               ****   *********   *********   *

=cut

=head1 SYNOPSIS

Usage: sidef [switches] [--] [programfile] [arguments]

  -c            compile the code into a Perl program
  -C            check syntax only
  -D            dump the syntax tree of a program
  -E program    one line of program
  -H            interactive help
  -i file       execute a program in interactive mode
  -k            keep track of potentially incorrect parser interpretations
  -M mode       set the rounding mode of floating-point numbers
                valid modes: [near], zero, inf, +inf, -inf, faith
  -N options    modify class-variables inside the Number class
                valid format: 'VERBOSE=1; USE_YAFU=1; USE_PRIMECOUNT=1'
  -o file       file where to dump the output
  -O level      perform code optimizations before execution
                valid levels: [0], 1, 2
  -P int        set the precision of floating-point numbers (default: 48)
  -r            parse and deparse a Sidef program
  -R lang       parse and deparse a Sidef program into a given language
                valid values: sidef, perl
  -s            save compiled code in a database to reduce boot-time
  -t            treat all command-line arguments as scripts
  -v            print version number and exit
  -w            enable warnings with stack backtrace
  -W            make warnings fatal (with stack backtrace)

Run 'sidef' for entering the interactive mode.

=head1 HELLO WORLD

A Sidef script can be written in any text editor and, by convention, it has the C<.sf> extension.

The content of a simple I<Hello World> program looks like this:

    say "Hello, 世界"

If we save the content in a new file called C<hello.sf>, we can execute the code by running:

    sidef hello.sf

=head1 ONE LINE OF PROGRAM

The C<-E code> command will execute the code specified as a command-line argument:

    sidef -E "say 'hello world'"

Outputs:

    hello world

=head1 ITERACTIVE MODE

The interactive mode (a.k.a. REPL) is available by simply executing the C<sidef> command, or by specifying the C<-i> command-line switch:

    $ sidef -i
    Sidef 24.11, running on Linux, using Perl v5.40.0.
    Type "help", "copyright" or "license" for more information.
    > n = 41
    #1 = 41
    > n**2 + n - 1
    #2 = 1721
    > is_prime(#2)
    #3 = true
    >

=head1 SPECIAL REPL COMMANDS

The REPL supports the following special commands:

=over 4

=item * Display the duration it took to execute the previous command:

    > ##

=item * Refer to a previous output value, using the C<#n> syntax (a negative value for C<n> is also supported):

    > 3+4
    #1 = 7
    > sqrt(#1)

=item * Load a Sidef file inside the REPL, line by line:

    > # load filename.sf

=item * Execute a Sidef file inside the REPL:

    > # exec filename.sf

=item * Save the code from the REPL inside a file:

    > # save filename.sf

=item * Reset the REPL:

    > reset

=item * Close the REPL:

    > quit

=back

=head1 OPTIMIZATION

The C<-O level> command-line option controls the level of optimization before the execution begins.

Currently, there are three levels of optimization available:

    0 -- Does nothing. (default)
    1 -- Does constant folding on the AST. (recommended)
    2 -- Does constant folding, after which it deparses the AST into Sidef code, parses the code again and does more constant folding on the new AST.

In the end, the code is translated to Perl and is ready to be executed. In the translation process, several other optimizations are also performed.

=head1 NUMBER OPTIONS

The C<-N> option can be used for changing the class-variables in the Number class:

    sidef -N 'PREC = 192'               # precision for floating-point numbers
    sidef -N 'ROUND = 0'                # rounding mode for floating-point numbers
    sidef -N 'VERBOSE = false'          # true to enable verbose/debug mode
    sidef -N 'USE_YAFU = false'         # true to use YAFU for factoring large integers
    sidef -N 'USE_PFGW = false'         # true to use PFGW64 as a primality pretest for large enough n
    sidef -N 'USE_PARI_GP = false'      # true to use PARI/GP in several methods
    sidef -N 'USE_FACTORDB = false'     # true to use factordb.com for factoring large integers
    sidef -N 'USE_PRIMESUM = false'     # true to use Kim Walisch's primesum in prime_sum(n)
    sidef -N 'USE_PRIMECOUNT = false'   # true to use Kim Walisch's primecount in prime_count(n)
    sidef -N 'USE_CONJECTURES = false'  # true to use conjectured methods for better performance
    sidef -N 'SPECIAL_FACTORS = true'   # true to try to find factors of special form in factor(n)

Multiple options can be separated with C<;>, as in:

    sidef -N 'VERBOSE = true; USE_FACTORDB = true' -E 'say factor(2**256 + 1)'

The C<-P> option can be used for changing the precision of floating-point numbers:

    sidef -P 1024 -E 'say sqrt(2)'

The C<-M> option can be used for changing the rounding-mode for floating-point numbers:

    sidef -M 'near'     # round to nearest (default)
    sidef -M 'zero'     # round towards zero
    sidef -M 'inf'      # round away from zero
    sidef -M '+inf'     # round towards +Infinity
    sidef -M '-inf'     # round towards -Infinity
    sidef -M 'faith'    # faithful rounding

=head1 PARSER WARNINGS

Sidef provides the C<-k> option which will keep track of all the possible incorrect parser interpretations.

For example, if we declare the following function, but we misspell its name when we call it, Sidef will interpret it as a method call, which is probably not what we want:

    func foo(n) { say n }
    fo(42)                   # will get interpreted as `42.fo`

When the command-line option C<-k> is specified, the following warning is produced:

    [INFO] `fo` is parsed as a prefix method-call at script.sf line 2

=head1 DEPARSING

Deparsing is the reverse process of parsing, which translates the AST back into code. Currently, Sidef supports deparsing into two languages with the C<-R lang> command-line switch:

=over 4

=item -R perl

Deparses the AST into valid Perl code.

=item -R sidef

Deparses the AST into valid Sidef code.

=back

Example:

    sidef -Rperl script.sf | perl

The C<-Rsidef> switch (or simply C<-r>) is useful for verifying how the code is parsed:

    sidef -r -E '1 + 2/3'

outputs:

    (1)->+((2)->/(3));

=head1 DUMPING THE AST

The C<-D> command-line option dumps the abstract syntax tree (AST) of a given Sidef program:

    sidef -D script.sf      # will dump the AST of script.sf

=head1 PRECOMPILATION

Sidef supports experimental precompilation by saving compiled code inside a database, which is updated automatically and sanitized periodically.

This method reduces significantly the boot-time of very large Sidef scripts, and it works as following:

=over 4

=item * it checks the database with the MD5 of the code

=item * if the MD5 exists inside the database, it returns the executable code

=back

otherwise:

=over 4

=item * parses the code and generates the executable code

=item * stores the executable code inside the database with the MD5 of the code

=back

Next time when the same code is executed, Sidef will simply retrieve the executable code from the database, without generating it again:

    sidef -s script.sf             # may load slow the first time
    sidef -s script.sf             # will load much faster the second time

=head1 COMPILATION

A Sidef script can be compiled to a stand-alone Perl program by using the C<-c> command-line option:

    sidef -o out.pl -c script.sf

The above command will compile the file C<script.sf> into the Perl script C<out.pl>, which will include the entire implementation code of Sidef.

Currently, Sidef code that contains C<eval()> cannot be compiled correctly to Perl, as it requires some parse-time information for run-time evaluation, which is lost in the compilation process.

=head1 WWW

You can find more info about Sidef, by clicking on the following links:

=over 2

=item * GitHub: L<https://github.com/trizen/sidef>

=item * Gitbook: L<https://trizen.gitbook.io/sidef-lang/>

=item * Tutorial: L<https://codeberg.org/trizen/sidef/wiki>

=item * RosettaCode: L<https://rosettacode.org/wiki/Sidef>

=back

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2013-2024 Daniel Șuteu, Ioana Fălcușan

This program is free software; you can redistribute it and/or modify it
under the terms of the B<Artistic License (2.0)>. You may obtain a copy
of the full license at:

L<https://www.perlfoundation.org/artistic-license-20.html>

Any use, modification, and distribution of the Standard or Modified
Versions is governed by this Artistic License. By using, modifying or
distributing the Package, you accept this license. Do not use, modify,
or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made
by someone other than you, you are nevertheless required to ensure that
your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service
mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge
patent license to make, have made, use, offer to sell, sell, import and
otherwise transfer the Package with respect to any patent claims
licensable by the Copyright Holder that are necessarily infringed by the
Package. If you institute patent litigation (including a cross-claim or
counterclaim) against any party alleging that the Package constitutes
direct or contributory patent infringement, then this Artistic License
to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=cut