NAME

JE - Pure-Perl ECMAScript (JavaScript) Engine

"JE" is short for "JavaScript::Engine."

VERSION

Version .001

WARNING: This module is still at an experimental stage. Only a few features have been implemented so far. The API is subject to change without notice.

Wait a minute! I shouldn't say that. I'll end up scaring people away. :-) If you have the time and the interest, please go ahead and experiment with this module and let me know if you have any ideas as to how the API might be improved (or redesigned if need be).

Right now about the only thing it can to is string concatenation (despite the fact that many more features are documented):

$ perl -MJE -le 'print JE->new->compile(q("aa" + "bb"))->execute->value'
aabb

Nice, isn't it?

SYNOPSIS

 use JE;

 $j = new JE;

 $j->prop(document => new JE::Object); # set property
 $j->prop('document'); # get a property

 $j->method(alert => "text"); # invoke a method

 $j->eval('{"this": "that", "the": "other"}["this"]');
 # returns "that"

 $compiled = $j->compile('new Array(1,2,3)');

 $compiled->execute;
 # returns [1,2,3]

 # create global functions:
 $j->new_function(correct => sub {
         my $x = shift;
         $x =~ y/AEae/EAea/;
         substr($x,1,3) =~ y/A-Z/a-z/;
         return $x;
 } );
 $j->new_function(print => sub { print @_, "\n" } );

 $j->eval('print(correct("ECMAScript"))'); # :-)
 

DESCRIPTION

This is a pure-Perl JavaScript engine. All JavaScript values (except undefined) are actually Perl objects underneath, that inherit from JE::Object. When you create a new JE object, you are basically creating a new JavaScript "world," the JE object itself being the global object. To add properties and methods to it, and to access those properties, see JE::Object, which this class inherits from.

If you want to create your own global object class (such as a web browser window), inherit from this class.

METHODS

new

This class method constructs and returns a new global scope (JE object).

compile( STRING )

compile parses the code contained in STRING and returns a parse tree (a JE::Code object [but I might rename JE::Code]).

The JE::Code class provides the method execute for executing the pre-compiled syntax tree.

eval ( STRING, return_obj => 1 )
eval ( STRING )

Note: return_obj has not been implemented. For now it is alwoys turned on and you can't turn it off.

eval evaluates the JavaScript code contained in string. This is just a wrapper around compile and the execute method of the JE::Code class. If return_obj is specified and is true, the return value will be the JavaScript object. Otherwise, eval will call the value method of the object, which produces a value that is more useful in Perl (see JE::Types). The following line:

$j->eval('[1,2,3]') # returns an array ref

is equivalent to

$j->eval('[1,2,3]', return_obj => 1)->value;

If an error occurs, undef will be returned and $@ will contain the error message. If no error occurs, $@ will be a null string.

VARIABLES

$JE::undef

The JavaScript undefined value.

$JE::null

The JavaScript null value.

WHAT STILL NEEDS TO BE FIGURED OUT

Constructor Functions

I've just noticed a problem. Right now, each object class has its own constructor function, and there is only one instance of it in Perl. That means that, when the parser supports enough syntax, JavaScript code can modify a constructor and the changes will be reflected in the constructor functions of other global scopes. I need to rethink part of my design....

How the Parser Should Work

I have not quite figured out how the JavaScript parser should work.

I could write a parser that parses the code and creates a parse tree. Then the execute subroutine could traverse the tree, executing code as it goes. The parse tree could contain line number information that would be used to generate helpful error messages.

But I think if I were to turn the parse tree into a Perl subroutine (at least for JavaScript functions; perhaps not for code that is run only once--when passed to eval), it would run a lot faster. The only problem is that I am not sure how to retain information needed for helpful error messages. I suppose I could put an eval { ... } or die "${$@} at line <number here>" around each statement. E.g., this function:

function copy_array(ary) {
        var new_ary = [];
        for(var i = 0; i < ary.length; ++i) {
                new_ary[i] = ary[i]
        }
        return new_ary
}

might, without error message support, become

sub {
        my($scope, $obj) = @_;
        $scope->var('new_ary', JE::Object::Array->new());
        for($scope->var('i',0); $scope->var('i') <
            $scope->var('ary')->prop('length'); ++$scope->var('i')) {
                $scope->var('new_ary')->prop(
                    $scope->var('i'), $scope->var('ary')->prop('i')
                );
        }
        return $scope->var('new_ary');
}

With eval blocks, it would become something like:

sub {
        my($scope, $obj) = @_;
        eval {
                $scope->var('new_ary', JE::Object::Array->new());
        } or die "${$@} on line 2";
        for(eval { $scope->var('i',0) or die "${$@} on line 3";
            eval { $scope->var('i') < $scope->var('ary')->prop('length')
                  } or die "${$@} on line 3";
            eval {++$scope->var('i')} or die "${$@} on line 3"
        ) {
            eval {
                $scope->var('new_ary')->prop(
                    $scope->var('i'), $scope->var('ary')->prop('i')
                );
            } or die "${$@} on line 4";
        }
        eval { return $scope->var('new_ary'); }
            or die "${$@} on line 6";
}

But that might slow things down considerably. (The fact that the code is messy doesn't matter, because it's computer-generated and shouldn't need to be read by a human.)

Or perhaps I could forget error messages altogether, since someone could just use Firefox for that instead. :-) Maybe I could provide the option of optimising the code, at the expense of simpler and less helpful error messages. "Slow mode" could be turned on for debugging.

Does anyone have any thoughts?

Tainting

Since the JavaScript code could come from a web page we need some way to make sure that it cannot access Perl functions directly. Of course, if my code were perfect, that would not be a problem. But is there any way to use perl's tainting features to prevent a leakage, in case of buggy code in this module?

Garbage Collection and Memory Leaks

I'm not sure how to go about removing circular references. Can anyone help?

Memoisation

Memoisation might help to speed things up a lot if applied to certain functions. The value method of JE::String, for instance, which has to put the string through the torturous desurrogification process, may benefit significantly from this.

But then it uses more memory. So maybe we could allow the user to use JE '-memoize'>, which could set the package var $JE::memoise to true. Then all subsequently require'd JE modules would check that var when they load.

PREREQUISITES

perl 5.8.0 or later

The parser uses the officially experimental (?{...}} and (??{...}) constructs in regexen. Considering that they are described the 3rd edition of Mastering Regular Expressions (and are not indicated therein as being experimental), I don't think they will be going away.

BUGS

Lots and lots.

There aren't enough features for this module to be usable yet.

The documentation is a bit incoherent. It probably needs a rewrite.

The author is not an expert on JavaScript, so there are probably some big conceptual errors here and there.

AUTHOR, COPYRIGHT & LICENSE

Copyright (C) 2007 Father Chrysostomos <sprout [at] cpan [dot] org>

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

3 POD Errors

The following errors were encountered while parsing the POD:

Around line 188:

You forgot a '=back' before '=head1'

Around line 330:

=over without closing =back

Around line 336:

=over without closing =back