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 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
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