NAME
JE - Pure-Perl ECMAScript (JavaScript) Engine
"JE" is short for "JavaScript::Engine."
VERSION
Version 0.003
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).
So far it supports expression statements. See the README file for a list of 'to-dos.'
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 inSTRING
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
Apart from the fact that there aren't enough features for this module to be usable yet, here are some known bugs:
Identifiers in JS source code that contain pairs of Unicode escape sequences representing surrogate pairs are currently not considered equivalent to the same identifiers with the actual characters instead of escape sequences. For example, '\ud801\udc00' is not considered the same as "\x{10400}", though it should be.
The JE::LValue and JE::Scope classes, which have AUTOLOAD
subs that delegate methods to the objects to which they refer, do not yet implement the can
method, so if you call $thing->can('to_string') on one of these you will get a false return value, even though these objects can to_string
.
The documentation is a bit incoherent. It probably needs a rewrite.
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 539:
You forgot a '=back' before '=head1'
- Around line 690:
=back without =over