The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -T

use 5.010;
use strict;
use warnings;

our $JavaScript_pm_available;
our $JE_pm_available;
our $JS_bin;

BEGIN {
    {
        eval { require JavaScript };
        $JavaScript_pm_available++ unless $@;

        eval { require JE };
        $JE_pm_available++ unless $@;

        eval {
            require File::Which;
            my @paths = File::Which::which("js");
            #($ENV{PATH}) = $ENV{PATH} =~ /(.*)/;
            $ENV{PATH} = "";
            for (@paths) {
                #print "# DEBUG Testing $_ ...\n";
                ($_) = /(.*)/;
                my $output = qx($_ -e 'print(JSON.stringify([1+1]))') or die;
                #print "# DEBUG Output: $output\n";
                if ($output =~ /\A\[2\]$/m) {
                    $JS_bin = $_;
                    last;
                }
            }
        };
        #$@ and die $@;

        # JavaScript.pm && JE.pm are not good/compatible enough: no
        # JSON object, etc.
        #last if $JavaScript_pm_available || $JE_pm_available || $JS_bin;
        last if $JS_bin;

        require Test::More;
        Test::More::plan(skip_all => "no usable JavaScript engines available");
    }
}

use Test::More tests => 189;
use Test::Exception;
use JSON;
use Language::Expr::Compiler::JS;
use String::ShellQuote;
use lib "./t";
require "testlib.pl";
require "stdtests.pl";

sub eval_in_js($$) {
    my ($js, $str) = @_;

    $str = $js->js($str);

    state $js_engine;

    if (!$js_engine) {
        if ($ENV{TEST_JS_ENGINE}) {
            $js_engine = $ENV{TEST_JS_ENGINE};
        } else {
            if ($JS_bin || $ENV{TEST_JS_ENGINE_BIN}) {
                $js_engine = "bin";
                $JS_bin = $ENV{TEST_JS_ENGINE_BIN} if $ENV{TEST_JS_ENGINE_BIN};
            } elsif ($JavaScript_pm_available) {
                $js_engine = "JavaScript.pm";
            } elsif ($JE_pm_available) {
                $js_engine = "JS.pm";
            }
        }
        print "# Choosing $js_engine".($js_engine eq 'bin' ? " ($JS_bin)": '')." as JavaScript engine\n";
    }

    if (0) {
    #if ($js_engine eq 'JavaScript.pm') {
    #    state $rt = JavaScript::Runtime->new;
    #    state $cx = $rt->create_context;
    #    return $cx->eval($str);
    #} elsif ($js_engine eq 'JE.pm') {
    #    state $je = JE->new;
    #    return $je->eval($str);
    } elsif ($js_engine eq 'bin') {
        my $cmd = qq($JS_bin -e ).shell_quote(qq($js->{_jscode_prefix}print(JSON.stringify($str)))).qq( 2>&1);
        #print "# DEBUG: $cmd\n";
        my $output;
        $output = qx($cmd);
        $output = "null" if $output =~ /\Aundefined\s*\z/s;
        $output =~ /syntax ?error/i and die "syntax error/invalid syntax: cmd=$cmd, output=$output";
        $output =~ /\b([A-Z]\w+Error)\b/i and die "javascript error: $1: cmd=$cmd, output=$output";
        $? and die "Can't execute $cmd successfully: $! ($?)";
        return convert_json_booleans(JSON->new->allow_nonref->decode($output));
    } else {
        die "BUG: Can't test yet with JS engine `$js_engine`!";
    }
}

my $js = new Language::Expr::Compiler::JS;
$js->{_jscode_prefix} = "let a=1; let b=2; let ary1=['one','two','three']; let hash1={one:1, two:2, three:3};";
$js->func_mapping->{floor} = 'Math.floor';
$js->func_mapping->{ceil}  = 'Math.ceil';
$js->func_mapping->{uc}  = '.toUpperCase';
$js->func_mapping->{length}  = ':length';

my @stdtests = stdtests();
#my @stdtests = (
#    {category=>'test', text=>'1+1', result=>2},
#);

for my $t (@stdtests) {
    my $tname = "category=$t->{category} $t->{text}";
    if ($t->{parse_error}) {
        $tname .= ", parse error: $t->{parse_error})";
        throws_ok { eval_in_js($js, $t->{text}) } $t->{parse_error}, $tname;
    } else {
        $tname .= ", js=q{".$js->{_jscode_prefix}.$js->js($t->{text})."}";
        if ($t->{run_error}) {
            $tname .= ", run error: $t->{run_error})";
            throws_ok { eval_in_js($js, $t->{text}) } $t->{run_error}, $tname;
        } elsif ($t->{js_compiler_run_error}) {
            $tname .= ", run error: $t->{js_compiler_run_error})";
            throws_ok { eval_in_js($js, $t->{text}) } $t->{js_compiler_run_error}, $tname;
        } elsif ($t->{compiler_run_error}) {
            $tname .= ", run error: $t->{compiler_run_error})";
            throws_ok { eval_in_js($js, $t->{text}) } $t->{compiler_run_error}, $tname;
        } else {
            $tname .= ")";
            my $res = exists($t->{js_result}) ? $t->{js_result} : $t->{result};
            is_deeply( eval_in_js($js, $t->{text}), $res, $tname );
        }
    }
}