NAME

JE - Pure-Perl ECMAScript (JavaScript) Engine

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

VERSION

Version 0.002

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 things you can do with it so far are:

- concatenate strings
- add numbers
- create arrays and objects with literals
- refer to global properties with identifiers
- refer to the global object with the 'this' keyword

(despite the fact that many more features are documented).

SYNOPSIS

 use JE;

 $j = new JE; # create a new global object

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

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

 $rv = $compiled->execute; # returns a JE::Object::Array
 $rv->value;               # returns a Perl array ref

 $obj = $j->eval('new Object');
 # create a new object

 $j->prop(document => $obj); # set property
 $j->prop(document => {});   # same thing (more or less)
 $j->prop('document'); # get a property

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

 # 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 are actually Perl objects underneath. 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::Types and 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 JE.

METHODS

$j = JE->new

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

$j->compile( STRING )

compile parses the code contained in STRING and returns a parse tree (a JE::Code object).

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

$j->eval ( STRING )

eval evaluates the JavaScript code contained in string. E.g.:

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

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

This is actually just
a wrapper around C<compile> and the C<execute> method of the
C<JE::Code> class.

Note: I'm planning to add an option to return an lvalue (a JE::LValue object), but I have yet to decide what to call it.

$j->new_function($name, sub { ... })
$j->new_function(sub { ... })

This creates and returns a new function written in Perl. If $name is given, it will become a property of the global object.

For more ways to create functions, see JE::Object::Function.

$j->upgrade( @values )

This method upgrades the value or values given to it. See "UPGRADING VALUES" in JE::Types for more detail.

If you pass it more than one argument in scalar context, it returns the number of arguments--but that is subject to change, so don't do that.

$j->undefined

Returns the JavaScript undefined value.

$j->null

Returns the JavaScript null value.

WHAT STILL NEEDS TO BE FIGURED OUT

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->new_var('new_ary', $scope->new_object('Array'));
        for($scope->new_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->new_var('new_ary', JE::Object::Array->new());
        } or die "${$@} on line 2";
        for(eval { $scope->new_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

I need to verify that running tainted JS code will, when executed, be checked by Perl's taint-checking mechanism. And if that's not the case, I need to figure out some way of making it work.

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 may redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

All the other JE:: modules, esp. JE::Object and JE::Types.

ECMAScript Language Specification (ECMA-262)

JavaScript.pm and JavaScript::SpiderMonkey--both interfaces to Mozilla's open-source SpiderMonkey JavaScript engine.

2 POD Errors

The following errors were encountered while parsing the POD:

Around line 533:

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

Around line 675:

=back without =over