glathoud said:
Hello, here is a code that transforms a tail call-recursive function
(growing stack depth) into a "flat" one (constant stack depth). This
brings also speed improvements on all JS engines I tested with
(Firefox, Safari, Chrome, IE). In case of interest:
http://glathoud.easypagez.com/publications/tailopt-js/tailopt-js.xhtml
1. Interesting idea.
2. There is no "Javascript". You are dealing with several ECMAScript
implementations here (among them JavaScript, JScript, Opera ECMAScript,
JavaScriptCore, and KJS), and you have yet to prove (you cannot) that
none of them (including, but not limited to, those mentioned) does not
make tail call optimization, before making such a statement.
As a result of that common misconception, you have falsely attributed
implementation-specific behavior to bugs in the runtime environments
(browsers) of the implementations that do not exhibit it, to bugs that
would need to be worked around, such as in (properly wrapped; your
source code SHOULD NOT exceed 80 characters per line)
eval( 'ret = (' + new_head + '{' + new_body + '})' );
// Ugly 'ret = (' because of IE
However, it is not "IE" that is the "culprit" here; instead, Microsoft
JScript implements the ECMAScript Language Specification rather
strictly, and
function (n) {...}
(as created by debugging the "Concise" example) is _not_ a syntactically
valid ECMAScript /Program/ as required by eval() so
eval("function (n) {...}");
MUST throw a SyntaxError exception (ES5, 15.1.2.1) *unless* an
implementation (those which you observed to be "working" without
the change) extends program syntax so that this is allowed, in
which case implementation-specific behavior is permitted instead
(section 16).
By placing the parentheses around the program code, and using
it in an assignment --
x = (function (n) {...})
--, it becomes producible by the /AssignmentExpression/ production;
therefore, a syntactically valid ECMAScript /Program/:
Program ::
SourceElement
SourceElement ::
Statement
Statement ::
ExpressionStatement
ExpressionStatement ::
[lookahead ∉ {{, function}] Expression
Expression ::
AssignmentExpression
AssignmentExpression ::
LeftHandSideExpression AssignmentOperator AssignmentExpression
LeftHandSideExpression ::
NewExpression
NewExpression ::
MemberExpression
MemberExpression ::
PrimaryExpression
PrimaryExpression ::
Identifier
AssignmentOperator ::
=
AssignmentExpression ::
ConditionalExpression
...
PrimaryExpression ::
( Expression )
Expression ::
MemberExpression
MemberExpression ::
FunctionExpression
3. `window' refers to a host object; do not try to augment it with
properties. Use a reference to the ECMAScript Global Object instead
or simply let the scope chain do its work. So instead of
(function () {
// ...
window.tailopt = function (...) {
// ...
};
// ...
)();
use either
var _global = this;
(function () {
// ...
_global.tailopt = function (...) {
// ...
};
// ...
)();
or simply
var tailopt;
(function () {
// ...
tailopt = function (...) {
// ...
};
// ...
)();
4. In the same vein, do not assume `window.console' and `console'
would be identical. And do not assume that because a property has
a true-value it must be callable; test for the type, catch
exceptions on call where necessary. For example, instead of
(wrapped)
var log = function () {
window.console && console.log && console.log.apply
&& console.log.apply( console, arguments );
},
use
/*
* See also the newsgroup's archives at e.g. Google Groups
* and use your favorite search engine to find more
* sophisticated implementations by contributors.
*/
function _isNativeMethod(o, p)
{
if (!o) return false;
return /\bfunction\b/i.test(typeof o[p]);
}
function _isHostMethod(o, p)
{
if (!o) return false;
var t = typeof o[p];
return (/\bunknown\b/i.test(t)
|| (/\b(function|object)\b/i.test(t) && o[p]));
}
function log()
{
/* but see also jsx.tryThis() for a compatibility wrapper */
try
{
typeof _global.console != "undefined"
&& _isHostMethod(console, "log")
&& _isNativeMethod(console.log, "apply")
&& console.log.apply(console, arguments);
}
catch (e)
{
throw new Error("Error console unavailable");
}
}
5. As you can also see here, you should not use function expressions where
function declarations suffice. And it might be better to prefix
identifiers that are only available in the local execution context,
or are user-defined, to distinguish them from others.
6. I don't see why a `while (true)' loop should be necessary anymore
to remove all comments with remove_comments() if you simply added
the global modifier to the RegExp initializer of the replace() call.
7. Code becomes easier to read and more obvious if you declare variables
where you initialize them, unless the execution contexts differ. If
you write
var rx = /^\s*(function\s*\(.*?\))\s*\{([\w\W]*?)\}\s*$/;
instead of
var ..., rx, ...;
/* [17 lines in-between] */
rx = /^\s*(function\s*\(.*?\))\s*\{([\w\W]*?)\}\s*$/;
it becomes obvious that you are initializing a local variable here.
8. In fact, I do not find that your overall code style improves
readability, rather the reverse:
if ( ! ( new RegExp( '^\\s*\\(\\s*' + fname + '\\s*=\\s*' ) ).test(
cond[ 1 ] ) ) {
// ...
}
as compared to (what I would prefer)
if (!(new RegExp('^\\s*\\(\\s*' + fname + '\\s*=\\s*')).test(cond[1]))
{
// ...
}
or
var rx = new RegExp('^\\s*\\(\\s*' + fname + '\\s*=\\s*');
if (!rx.test(cond[1]))
{
// ...
}
HTH
PointedEars