#!perl

use strict;
use warnings;

use SPVM::Builder::Util;
use SPVM::Global;
use FindBin;

SPVM::Builder::Util::getopt
  # spvmcc and spvm shared
  'h|help'          => \my $help,
  'v|version'       => \my $show_version,
  'I|include-dir=s' => \my @include_dirs,
  'B|build-dir=s'   => \my $build_dir,
  'w'               => \my $warning,
  
  # spvm only
  'e=s' => \my $source,
  '-M=s' => \my @use_class_names,
  '-c' => \my $syntax_ok,
;

if ($help) {
  print SPVM::Builder::Util::extract_usage;
  exit 0;
}
elsif ($show_version) {
  my $version_string = "spvm v$SPVM::VERSION";
  print "$version_string\n";
  exit 0;
}

if (defined $build_dir) {
  $ENV{SPVM_BUILD_DIR} = $build_dir;
}

my $script_name = shift;

if (!defined $script_name && !defined $source && !defined $source) {
  die SPVM::Builder::Util::extract_usage;
}

if (defined $script_name && defined $source) {
  die "[spvm command]Specifying both SCRIPT_NAME and -e option is not allowed.";
}

if (defined $script_name && defined $source) {
  die "[spvm command]Specifying both SCRIPT_NAME and -e option is not allowed.";
}

if (defined $source) {
  $0 = "-e";
}
elsif (defined $script_name) {
  
  $0 = $script_name;
  
  open my $script_fh, '<', $script_name
    or die "Can't open file \"$script_name\":$!";
    
  $source = do { local $/; <$script_fh> };
  
  $source = "#file \"$script_name\"\x{A}$source";
}

FindBin::again();

my $include_dirs_by_lib_directive = SPVM::Builder::Util::parse_lib_directive($source, $FindBin::Bin);

unshift @INC, map { $_ =~ s/[\\\/]SPVM$//; $_; } @include_dirs, @$include_dirs_by_lib_directive;

if ($warning) {
  $^W = 1;
}

for my $use_class_name (@use_class_names) {
  SPVM::Global::build_class($use_class_name, __FILE__, __LINE__);
}

SPVM::Global::build_class('Native', __FILE__, __LINE__);
SPVM::Global::build_class('Native::MethodCall', __FILE__, __LINE__);

my $anon_class_name = SPVM::Global::build_anon_class($source, __FILE__, __LINE__);

SPVM::Native->check_bootstrap_method($anon_class_name);

my $spvm_anon_class_name = "SPVM::$anon_class_name";

if ($syntax_ok) {
  print STDERR "syntax OK\n";
  exit 0;
}

$spvm_anon_class_name->main;

=encoding utf8

=head1 Name

spvm - Executing SPVM programs

=head1 Description

The spvm command executes SPVM programs.

=head1 Usage

  Usage: spvm [OPTIONS] SCRIPT_NAME
    
    spvm myapp.spvm
    
  Options:
    -h, --help                     Shows this message
    -v, --version                  Shows the version
    -I, --include-dir DIRECTORY    Adds a include directory
    -B, --build-dir DIRECTORY      Build diretory
    -e SOURCE                      Executes a program source code in main method.
    -c                             Check syntx only

=head1 Details

  spvm [OPTIONS] SCRIPT_NAME

The C<spvm> command executes a SPVM program.

I<OPTIONS> are L<options|/"Options">.

I<SCRIPT_NAME> is a script path that contains an SPVM script.
  
  # myapp.spvm
  use Fn;
  my $var = 1;
  say $var;

Anon class that contains a L<bootstrap method|SPVM::Document::Language::Class/"Bootstrap Method"> is allowed as an SPVM script.

  # myapp.spvm
  class {
    
    version "1.001";
    
    our $FOO : int;
    
    use Fn;
    
    static method main : void () {
      my $var = 1;
      say $var;
    }
  }

The base name of I<SCRIPT_NAME> must be a L</"Script Base Name">. Otherwise an exception is thrown.

See L<Class Search Directories|SPVM::Document::Language::Class/"Class Search Directories"> about default class search directories.

See L<SPVM::Document::EnvironmentVariables> about available environment variables.

=head2 Script Base Name

A script base name ends with C<.spvm>.

A script base name without C<.spvm> consists of L<word characters|SPVM::Document::Language::Tokenization/"Word Characters"> and C<->.

It dose not contains C<__>.

It dose not begin with C<0-9>.

It dose not begin with C<->.

It dose not end with C<->.

It dose not contains C<-->.

Examples:
  
  # Script Base Names
  foo.spvm
  foo_bar2.spvm
  myapp-foo.spvm
  
  # Invalid Script Base Names
  2foo.spvm
  foo__bar.spvm
  -myapp.spvm
  myapp-.spvm
  myapp--foo.spvm

=head1 Options

=head2 --help

Outputs how to use the C<spvm> command to standard output.

=head2 -h

  -h

Same as L</"--help">.

=head2 --version

Outputs the version of the C<spvm> command to standard output. This version is the same as the version of L<SPVM>.

=head2 -v

  -v

Same as L</"--version">.

=head2 --include-dir

  --include-dir DIRECTORY

Prepends I<DIRECTORY> to L<class search directories|SPVM::Document::Language::Class/"Class Search Directories">

This option can be specified multiple times.

  --include-dir dir1 --include-dir dir2

In this case, class search directories becomes the following.

  [dir1, dir2, default_dirs]

=head2 -I

  -I DIRECTORY

Same as L</"--include-dir">.

=head2 --build-dir

  --build-dir DIRECTORY

Sets L<SPVM_BUILD_DIR|SPVM::Document::EnvironmentVariables/"SPVM_BUILD_DIR"> environment variable to I<DIRECTORY>.

=head2 -B

  -B DIRECTORY

Same as L</"--build-dir">.

=head2 -e

  -e SOURCE

Executes a source code I<SOURCE> in C<main> method.

Examples:

  spvm -e 'say "Hello World!";';

=head2 -c

  -c

Checks syntax only.

=head2 -w

  -w

Enables warning flag.

Implementation:

Sets L<CommandInfo#WARNING|SPVM::CommandInfo> class variable to 1.

=head1 lib Directive

If the source code specified by I<SCRIPT_NAME>, or C<-e> option contains lib directives, The directories specified by lib directive is prepeneded to L<class search directories|SPVM::Document::Language::Class/"Class Search Directories">.
  
  #lib "$FindBin::Bin/lib"
  
This directories specified by lib directive is placed after the directories specified by L</"--include-dir"> option.

=head1 Copyright & License

Copyright 2023 Yuki Kimoto. All Rights Reserved.

MIT License.
