Javascript object get code as string - javascript

First off, I am sorry if this is a duplicate, but every time I googled for 'object' and 'code' I got tutorial pages.
I want to know if there is any easy way to get the code associated with an object. Something like
function A(){
this.name = 'Kaiser Sauze';
}
a = new A();
console.log(a.displayCode());
//OUTPUT
"function A(){ this.name = 'Kaiser Sauze';}"
I want to be able to view the code, modify it and reload the function, all from within the browser. I wanted to know if there was some way to do this, or if I have to prime the pump by doing something like this:
function A(){
this.name = 'Kaiser Sauze';
this.code = "function A(){ this.name = 'Kaiser Sauze';}"
}
then every time the user loads up the text editor to view this.code I connect the onchange to update this.code.
EDIT
turns out yankee suggested a simple solution to this
function A(x){
this.x = x ;
}
console.log(A.toString());
//OUTPUT
"function A(x){
this.x = x ;
}"
but in my implementation the variable 'x' can be a function (actually a complicated object with variables, functions and sub objects which I mix in via a call to dojo.mixin), so what I really want is to know the code when instantiated, something like so
function A(x){
this.x = x ;
}
var a = new A(function(){/*DO SOMETHING*/);
console.log(a.toString());
//OUTPUT
"var a = new A(function(){/*DO SOMETHING*/);"
but, as most of you already know, all that gets output is something like "Object". I have almost found a way around this, by putting the initialization in a function like so
function A(x){
this.x = x ;
}
function _A(){
var a = new A(function(){/*DO SOMETHING*/);
}
console.log(_A.toString());
//OUTPUT
"function _A(){
var a = new A(function(){/*DO SOMETHING*/);
}"
but that is confusing, and then I have to go in and start parsing the string which I do not want to do.
EDIT: The reason I ask all of this is b/c I want to make code that is both dynamically executable and highly modular. I am dealing with the canvas. I want the user to be able to click on a, for example, rectangle, view its code, and modify and then load/execute it. I have a series of rules but basically I have a shape class and everything that defines that shape (color, transparency, fills, strokes...) has to get passed as a parameter to the object cosntructor, something like:
rect = new Shape({color : 'rgba(0,0,0,1)' ,
x : 0 ,
y : 0 ,
w : 100 ,
h : 100 ,
draw : function() {ctx.fillStyle = this.color;
ctx.fillRect(this.x,this.y,this.w,this.h);
}
});
This way the code is automatically modular, I don't have to worry about the color being defined at the top of the page, and then the height being defined half way down the page, and so on. Now the only thing I need is to somehow, pass as a parameter, the entire above string representation of the initialization. I could wrap it in a function and call toString on that, like so
function wrapper(){
rect = new Shape({color : 'rgba(0,0,0,1)' ,
x : 0 ,
y : 0 ,
w : 100 ,
h : 100 ,
draw : function() {ctx.fillStyle = this.color;
ctx.fillRect(this.x,this.y,this.w,this.h);
},
code : wrapper.toString()
});
}
but then there are two problems. 1) I have to manually remove the function wrapper() and trailing } as well as moving every line to the left by one tab. 2) there is no guarantee that a user will remember to include the wrapper function as it is totally unecessary for purposes of drawing. I am trying to think of a way where the wrapper would seem natural, but I can't think of any. But then again I haven't slept in over 30 hours.

OK... Reviewing again... I think that's what you want ;-).
>>> function A() {this.name ="foo";}
undefined
>>> A.toString()
"function A() {this.name ="foo";}"

Refer to code snippet below:
function A() {
this.name = 'Kaiser Sauze';
}
a = new A();
console.log(a.constructor.toString());
// output
// "function A(){
// this.name = 'Kaiser Sauze';
// }"
When you do new A(), function A becomes the constructor of object a. Thus, you can reference function A via the constructor property of object a.
Read more about Javascript constructor on MDN.

Edit: added pretty-printing.
You could JSON.stringify() the argument of the constructor, if it is JSON-compatible. Here is a toString() function that builds on this idea, but with a slightly generalized version of JSON.stringify() that accepts stringifying functions:
function Shape(x){
this.x = x;
}
Shape.prototype.toString = function() {
function stringify(data, prefix) {
function unicode_escape(c) {
var s = c.charCodeAt(0).toString(16);
while (s.length < 4) s = "0" + s;
return "\\u" + s;
}
if (!prefix) prefix = "";
switch (typeof data) {
case "object": // object, array or null
if (data == null) return "null";
var i, pieces = [], before, after;
var indent = prefix + " ";
if (data instanceof Array) {
for (i = 0; i < data.length; i++)
pieces.push(stringify(data[i], indent));
before = "[\n";
after = "]";
}
else {
for (i in data)
pieces.push(i + ": " + stringify(data[i], indent));
before = "{\n";
after = "}";
}
return before + indent
+ pieces.join(",\n" + indent)
+ "\n" + prefix + after;
case "string":
data = data.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
.replace(/\n/g, "\\n").replace(/\r/g, "\\r")
.replace(/\t/g, "\\t")
.replace(/[\x00-\x19]/g, unicode_escape);
return '"' + data + '"';
default:
return String(data).replace(/\n/g, "\n" + prefix);
}
}
return "new Shape(" + stringify(this.x) + ")";
};
var rect = new Shape({color : 'rgba(0,0,0,1)' ,
x : 0 ,
y : 0 ,
w : 100 ,
h : 100 ,
draw : function() {ctx.fillStyle = this.color;
ctx.fillRect(this.x,this.y,this.w,this.h);
}
});
console.log(rect.toString());
The output is:
new Shape({
color: "rgba(0,0,0,1)",
x: 0,
y: 0,
w: 100,
h: 100,
draw: function() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
})

I can't believe no one suggested this (I apologize if this answer is somewhere in between the lines). I didn't think of it because at the time of development, all my work was clientside. All I really have to do is load the code once with Ajax as javascript. Once it is loaded and an object created, I load it again as a string and assign it to a variable in the object.

Here is another solution that might work (using yankee's example). I am however unsure what happens if A already exists? Perhaps you should do a "delete(A);" before saving it in the storage.
// Create A
// function A() {this.name = "foo";}
var x = 'function A() {this.name = "foo";}'; // Store function to variable using A.toString();
// Save x in storage
// -- Page reload --
// Load and eval x from storage
eval(x);
var a = new A(); // Use A
alert(a.name);

function dumpObject(obj)
{
var output = "";
for (var i in obj)
{
var msg = i + "= " + obj[i];
output += msg + "\n";
}
return output;
}
var myObject = {
myName: 'Daniel',
get_name: function()
{
return this.myName;
}
}
alert( dumpObject(myObject) );
//this will output:
//
// myName= Daniel
// get_name=function()
// {
// return this.myName;
// }
Here is my fiddle for that:
http://jsfiddle.net/DanielDZC/vXrQf/

I wouldn't recommend using on a production server, only for testing and debugging, but there is a method named toSource() which returns the source of a function as a String:
function add(a, b) {
return a + b;
}
console.log(add.toSource());
Outputs:
function add(a, b) { return a + b; }
Available on JS Fiddle: http://jsfiddle.net/IQAndreas/dP453/1/
Note that Mozilla has marked this method as non-standard (which if you read the details means "Only works in FireFox").

Sounds like you're looking for Reflection and/or Introspection support. I'm not sure where the other major engines are at in this regards but SpiderMonkey's Parser API was recently referenced in an article on Extension Introspection with Chrome Privileges.

Related

How to create an object where value is a function?

I need to programatically create an object that got one pair of property : value like tmpCol["formatter"]=color where color is defined like
var colors = function backgroundColor(cell, formatterParams){}
everything works fine on jsFiddle
But I need to create the object on the fly and outside the javascript. I using Google Script where I create definition of column headers based on data. I tried to define a dummy function var colors = function backgroundColor(cell, formatterParams){} on the server side so the definition would even pass a run. But when I run the page in a browser the function definition seems to me to be a text rather a function. The browser makes a call the get the data using google.script.run the definition is returned by GAS.
Typed definition tmpCol["formatter"]=colors; works but not the one when processed by GAS
The html code on server side looks like
<script>
var colors = function backgroundColor(cell, formatterParams){
var value = cell.getValue();
if ("color" in formatterParams) {
var color = cell.getRow().getData()[formatterParams["color"]];
if (color){
cell.getElement().style.backgroundColor = cell.getRow().getData()[formatterParams["color"]];
}
}
return value;
}
var initialTableData = [{id:0,name:"nahrávají se data"}];
var table = new Tabulator("#zz-test", {
layout:"fitColumns",
responsiveLayout:"hide",
data:initialTableData,
dataTree:true,
selectable:true,
columns:[
{title:"",
field:"name",
headerSort:false,
},
],
});
google.script.run.withSuccessHandler(updateTable).callLibraryFunction("zzlib.getTabulatorData",'A87J8HRS1');
function updateTable(data){
table.deleteColumn("name");
for ( var c = 0; c < data.tableColumns.length; c++){
table.addColumn(data.tableColumns[c]);
}
//test
var tmpCol = {};
tmpCol["title"]="test 1";
tmpCol["field"]="test1";
tmpCol["headerSort"]=false;
tmpCol["formatter"]=colors;
tmpCol["formatterParams"]={color:"color1"};
columns = [
{title:"", field:"name", width:200},
tmpCol,
{title:"Test2", field:"test2", width:200,formatter:colors,formatterParams:{color:"color2"}}
];
// table.deleteColumn("name");
// table.deleteColumn("test1");
// table.deleteColumn("test2");
for ( var c = 0; c < columns.length; c++){
// table.addColumn(columns[c]);
}
console.log(columns);
// test
table.setData(data.tableData);
}
</script>
I need to define formatter for two columns. So inside the for cycle I tried these so far.
for ( var c = 0; c < data.tableColumns.length; c++){
if ("formatter" in data.tableColumns[c]) {
//console.log(eval(data.tableColumns[c]["formatter"]));
//data.tableColumns[c]["formatter"]= new Function("return " + data.tableColumns[c]["formatter"]);
data.tableColumns[c]["formatter"]= var colors = new Function("return " + data.tableColumns[c]["formatter"]);
//eval(data.tableColumns[c]["formatter"]);
//eval("var colors = " + data.tableColumns[c]["formatter"]);
console.log("eval");
}
}
Any idea how to create the object value on the server side and pass it correctly to the browser?
Assuming you have:
var functionString = "function backgroundColor(cell, formatterParams){}";
You can use Function constructor: #Recommended
var colors = new Function("return " + functionString)()
This parses our function inside an anonymous function, we add return so by calling the anonymous function we can get our target function, and in the end we add the call ()
example:
var functionString = "function backgroundColor(cell, formatterParams){ return 'Hey!'}";
var anonymousFunction = new Function("return " + functionString);
var targetFunction = anonymousFunction();
console.log(anonymousFunction);
console.log(anonymousFunction());
console.log(targetFunction);
console.log(targetFunction());
Read more here
or you can use eval: # Not Recommended
eval("var colors = " + functionString);
This one will parse and evaluate your string as a javascript code
However, this is a risky approach, try to avoid it
Read more here

Nested function as method in javascript

I'm trying hard to understand basic concepts of javascript. The code below seems to be working fine if I use only "gear += 1" in line 8 below, but I cannot understand why is this not working when I'm using "this.gear += 1". It gives result as NaN. Thank you.
(function bike(speed, tank, gear) {
var i = {};
i.speed = speed;
i.tank = tank;
i.gear = gear;
i.addgear = (function() {
// works fine with "return gear+= 1" Why not with "this"?
return this.gear += 1;
})();
console.log("mybike", i);
})(120, 12, 5);
There are many ways to achieve what you're looking for, including the class keyword from ES2015 and up or the prototype system that underlies it. Here's a very simple sample:
function bike(speed, tank, gear) {
return {speed, tank, gear, addGear: function() {return this.gear += 1}}
}
const myBike = bike(120, 12, 5)
console.log(myBike);
myBike.addGear();
console.log(myBike)
Yours doesn't work for several reasons. First of all, you never return anything out of your outermost function. Secondly, you create and immediately execute a function whose output then becomes your addGear value. The simplest fix to your code would be something like this:
function bike(speed, tank, gear) {
var i = {};
i.speed = speed;
i.tank = tank;
i.gear = gear;
i.addgear = function() {
return this.gear += 1;
};
return i;
}
That would be equivalent to what I wrote above.

JavaScript: Implementing Callback Functions

This is a three part question.
In the code shown below, whenever I execute the functions (walk, run, crawl) I am observing that it is displaying the output for the method distance_travelled in a cumulative manner:
Trey says thank you
Trey walked a distance of 3
Trey ran a distance of 13
Trey crawled a distance of 16
Trey ran a distance of 26
I would like to ensure that each function calculates the distance by considering the method distance_travelled to be initialized to 0.
My second question is related to the callback function.
I am trying to create another property/method called doSomething() and have this method return a random function back (walk, run, crawl).
For example if I execute the following code:
var returned_function = person.doSomething();
returned_function();
It should execute one of the three methods. I have managed to execute the method run(). However, when I run the code in my browser, the alert pop up message displays undefined. Also, I encounter the same issue as in my first question. It calculates distance_travelled in a cumulative manner. How can I solve this?
My third question. I am trying to add a new method called 'fly' to the person object. The 'fly' method takes two functions as arguments.
I have to give a 30% chance for the person to fly. The function fly method should execute if the person is successfully able to fly (30% chance that this can happen). The second function should execute if the person is NOT able to fly (70% chance this would happen).
How can I implement this functionality into my code? Can someone suggest how to approach this problem?
<script type="text/javascript">
var person = new Object();
person.name = "Trey";
person.distance_travelled = 0;
person.say_name = alert(person.name);
person.say_something = function(xyz) {
document.write(person.name + " says " + xyz + '<br>');
}
person.say_something("thank you");
person.walk = alert(person.name + " is walking");
function walk(){
person.distance_travelled +=3;
document.write(person.name + " walked a distance of " + person.distance_travelled + '<br>');
}
walk();
person.run = alert(person.name + " is running");
function run(){
person.distance_travelled +=10;
document.write(person.name + " ran a distance of " + person.distance_travelled + '<br>');
}
run();
person.crawl = alert(person.name + " is crawling");
function crawl(){
person.distance_travelled +=3;
document.write(person.name + " crawled a distance of " + person.distance_travelled + '<br>');
}
crawl();
person.doSomething = function(abc){
alert(run());
}
var returned_function = person.doSomething();
returned_function();
</script>
First of all:
person.walk = alert(person.name + " is walking");
probably does nothing like what you think it would (because I can't ever think a line like that might make sense).
Your first question is trivial. If you want to output 3, just output 3, not distance_travelled.
Second question:
var activities = ['run', 'walk', 'crawl'];
person.doSomething = function() {
var randomActivity = activities[Math.floor(Math.random() * activities.length)];
return function() {
this[randomActivity]();
}
}
var personDoActivity = person.doSomething();
personDoActivity();
or
person.doSomething = function() {
var activities = [this.run, this.walk, this.crawl];
var randomActivity = activities[Math.floor(Math.random() * activities.length)];
return randomActivity;
}
var doActivity = person.doSomething();
doActivity.call(person);
Third question (and I'm changing it from object-oriented to procedural because you're not using OO correctly, anyway):
function maybeFly(fly, noFly) {
if (Math.random() < 0.3) {
return fly();
} else {
return noFly();
}
}
Because I still can't comment on answers -.-
#Amadan
the second version of doSomething can maybe be even nicer using .bind()
person.doSomething = function() {
var activities = [this.run, this.walk, this.crawl];
var randomIndex = Math.floor(Math.random() * activities.length);
return activities[randomIndex].bind(this);
}
var doActivity = person.doSomething();
doActivity();
Also worth noting that it is better to use, and more in the spirit of javascript to write
var person = {};
instead of
var person = new Object();

who can help me optimized this javascript code?

this is my part code,but a friend say the variable(like getStyle,getOffsetWidth,getOffsetHeight,log) will not release, so i want know why the variable will not release,and how to optimized it,thanks!
var Util = (function() {
"use strict";
var getStyle = function(node) {
var style = null;
if (window.getComputedStyle) {
style = window.getComputedStyle(node, null);
} else {
style = node.currentStyle;
}
return style;
};
var getOffsetWidth = function(style) {
return parseInt(style.width, 10) +
parseInt(style.paddingLeft, 10) +
parseInt(style.paddingRight, 10) +
parseInt(style.marginLeft, 10) +
parseInt(style.marginRight, 10);
};
var getOffsetHeight = function(style) {
return parseInt(style.height, 10) +
parseInt(style.paddingTop, 10) +
parseInt(style.paddingBottom, 10) +
parseInt(style.marginTop, 10) +
parseInt(style.marginBottom, 10);
};
var log = function() {
if (window.console && window.console.log) {
window.console.log(arguments);
}
};
return {
getStyle: getStyle,
getOffsetWidth: getOffsetWidth,
getOffsetHeight: getOffsetHeight,
log: log
};
}());
Your friend is probably referring to the fact that the variables getStyle, getOffsetWidth, etc are included in the closure of the returned methods. This is a tiny bit inefficient because those variables are never used again.
In a simple case like this, where the functions in your Util object are not making any use of the closure of the outer function, there's no reason not to just do:
var Util = {
getStyle: function(style) {
return parseInt(style.width) + ...
},
getOffsetWidth: ...
};
Yes, that's a module written in the Module Pattern.
The self-exeuting anonymous function forms a closure - or a set of closures if you are so minded (I'm not) - establishing four privileged methods exposed via the return expression as properties of Util.
This pattern may also include private vars/methods, which are established in exactly the same scope as getStyle, getOffsetWidth etc, but are not exposed via the return expression.
This is a perfectly valid way to establish one or more singleton "namespace" objects, which is precisely the objective of the Module Pattern.

Extracting nested function names from a JavaScript function

Given a function, I'm trying to find out the names of the nested functions in it (only one level deep).
A simple regex against toString() worked until I started using functions with comments in them. It turns out that some browsers store parts of the raw source while others reconstruct the source from what's compiled; The output of toString() may contain the original code comments in some browsers. As an aside, here are my findings:
Test subject
function/*post-keyword*/fn/*post-name*/()/*post-parens*/{
/*inside*/
}
document.write(fn.toString());
Results
Browser post-keyword post-name post-parens inside
----------- ------------ --------- ----------- --------
Firefox No No No No
Safari No No No No
Chrome No No Yes Yes
IE Yes Yes Yes Yes
Opera Yes Yes Yes Yes
I'm looking for a cross-browser way of extracting the nested function names from a given function. The solution should be able to extract "fn1" and "fn2" out of the following function:
function someFn() {
/**
* Some comment
*/
function fn1() {
alert("/*This is not a comment, it's a string literal*/");
}
function // keyword
fn2 // name
(x, y) // arguments
{
/*
body
*/
}
var f = function () { // anonymous, ignore
};
}
The solution doesn't have to be pure regex.
Update: You can assume that we're always dealing with valid, properly nested code with all string literals, comments and blocks terminated properly. This is because I'm parsing a function that has already been compiled as a valid function.
Update2: If you're wondering about the motivation behind this: I'm working on a new JavaScript unit testing framework that's called jsUnity. There are several different formats in which you can write tests & test suites. One of them is a function:
function myTests() {
function setUp() {
}
function tearDown() {
}
function testSomething() {
}
function testSomethingElse() {
}
}
Since the functions are hidden inside a closure, there's no way for me invoke them from outside the function. I therefore convert the outer function to a string, extract the function names, append a "now run the given inner function" statement at the bottom and recompile it as a function with new Function(). If the test function have comments in them, it gets tricky to extract the function names and to avoid false positives. Hence I'm soliciting the help of the SO community...
Update3: I've come up with a new solution that doesn't require a lot of semantic fiddling with code. I use the original source itself to probe for first-level functions.
Cosmetic changes and bugfix
The regular expression must read \bfunction\b to avoid false positives!
Functions defined in blocks (e.g. in the bodies of loops) will be ignored if nested does not evaluate to true.
function tokenize(code) {
var code = code.split(/\\./).join(''),
regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+/mg,
tokens = [],
pos = 0;
for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
var match = matches[0],
matchStart = regex.lastIndex - match.length;
if(pos < matchStart)
tokens.push(code.substring(pos, matchStart));
tokens.push(match);
}
if(pos < code.length)
tokens.push(code.substring(pos));
return tokens;
}
var separators = {
'/*' : '*/',
'//' : '\n',
'"' : '"',
'\'' : '\''
};
function extractInnerFunctionNames(func, nested) {
var names = [],
tokens = tokenize(func.toString()),
level = 0;
for(var i = 0; i < tokens.length; ++i) {
var token = tokens[i];
switch(token) {
case '{':
++level;
break;
case '}':
--level;
break;
case '/*':
case '//':
case '"':
case '\'':
var sep = separators[token];
while(++i < tokens.length && tokens[i] !== sep);
break;
case 'function':
if(level === 1 || (nested && level)) {
while(++i < tokens.length) {
token = tokens[i];
if(token === '(')
break;
if(/^\s+$/.test(token))
continue;
if(token === '/*' || token === '//') {
var sep = separators[token];
while(++i < tokens.length && tokens[i] !== sep);
continue;
}
names.push(token);
break;
}
}
break;
}
}
return names;
}
The academically correct way to handle this would be creating a lexer and parser for a subset of Javascript (the function definition), generated by a formal grammar (see this link on the subject, for example).
Take a look at JS/CC, for a Javascript parser generator.
Other solutions are just regex hacks, that lead to unmaintainable/unreadable code and probably to hidden parsing errors in particular cases.
As a side note, I'm not sure to understand why you aren't specifying the list of unit test functions in your product in a different way (an array of functions?).
Would it matter if you defined your tests like:
var tests = {
test1: function (){
console.log( "test 1 ran" );
},
test2: function (){
console.log( "test 2 ran" );
},
test3: function (){
console.log( "test 3 ran" );
}
};
Then you could run them as easily as this:
for( var test in tests ){
tests[test]();
}
Which looks much more easier.
You can even carry the tests around in JSON that way.
I like what you're doing with jsUnity. And when I see something I like (and have enough free time ;)), I try to reimplement it in a way which better suits my needs (also known as 'not-invented-here' syndrome).
The result of my efforts is described in this article, the code can be found here.
Feel free to rip-out any parts you like - you can assume the code to be in the public domain.
The trick is to basically generate a probe function that will check if a given name is the name of a nested (first-level) function. The probe function uses the function body of the original function, prefixed with code to check the given name within the scope of the probe function. OK, this can be better explained with the actual code:
function splitFunction(fn) {
var tokens =
/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/
.exec(fn);
if (!tokens) {
throw "Invalid function.";
}
return {
name: tokens[1],
body: tokens[2]
};
}
var probeOutside = function () {
return eval(
"typeof $fn$ === \"function\""
.split("$fn$")
.join(arguments[0]));
};
function extractFunctions(fn) {
var fnParts = splitFunction(fn);
var probeInside = new Function(
splitFunction(probeOutside).body + fnParts.body);
var tokens;
var fns = [];
var tokenRe = /(\w+)/g;
while ((tokens = tokenRe.exec(fnParts.body))) {
var token = tokens[1];
try {
if (probeInside(token) && !probeOutside(token)) {
fns.push(token);
}
} catch (e) {
// ignore token
}
}
return fns;
}
Runs fine against the following on Firefox, IE, Safari, Opera and Chrome:
function testGlobalFn() {}
function testSuite() {
function testA() {
function testNested() {
}
}
// function testComment() {}
// function testGlobalFn() {}
function // comments
testB /* don't matter */
() // neither does whitespace
{
var s = "function testString() {}";
}
}
document.write(extractFunctions(testSuite));
// writes "testA,testB"
Edit by Christoph, with inline answers by Ates:
Some comments, questions and suggestions:
Is there a reason for checking
typeof $fn$ !== "undefined" && $fn$ instanceof Function
instead of using
typeof $fn$ === "function"
instanceof is less safe than using typeof because it will fail when passing objects between frame boundaries. I know that IE returns wrong typeof information for some built-in functions, but afaik instanceof will fail in these cases as well, so why the more complicated but less safe test?
[AG] There was absolutely no legitimate reason for it. I've changed it to the simpler "typeof === function" as you suggested.
How are you going to prevent the wrongful exclusion of functions for which a function with the same name exists in the outer scope, e.g.
function foo() {}
function TestSuite() {
function foo() {}
}
[AG] I have no idea. Can you think of anything. Which one is better do you think? (a) Wrongful exclusion of a function inside. (b) Wronfgul inclusion of a function outside.
I started to think that the ideal solution will be a combination of your solution and this probing approach; figure out the real function names that are inside the closure and then use probing to collect references to the actual functions (so that they can be directly called from outside).
It might be possible to modify your implementation so that the function's body only has to be eval()'ed once and not once per token, which is rather inefficient. I might try to see what I can come up with when I have some more free time today...
[AG] Note that the entire function body is not eval'd. It's only the bit that's inserted to the top of the body.
[CG] Your right - the function's body only gets parsed once during the creation of probeInside - you did some nice hacking, there ;). I have some free time today, so let's see what I can come up with...
A solution that uses your parsing method to extract the real function names could just use one eval to return an array of references to the actual functions:
return eval("[" + fnList + "]");
[CG] Here is with what I came up. An added bonus is that the outer function stays intact and thus may still act as closure around the inner functions. Just copy the code into a blank page and see if it works - no guarantees on bug-freelessness ;)
<pre><script>
var extractFunctions = (function() {
var level, names;
function tokenize(code) {
var code = code.split(/\\./).join(''),
regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+|\\/mg,
tokens = [],
pos = 0;
for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
var match = matches[0],
matchStart = regex.lastIndex - match.length;
if(pos < matchStart)
tokens.push(code.substring(pos, matchStart));
tokens.push(match);
}
if(pos < code.length)
tokens.push(code.substring(pos));
return tokens;
}
function parse(tokens, callback) {
for(var i = 0; i < tokens.length; ++i) {
var j = callback(tokens[i], tokens, i);
if(j === false) break;
else if(typeof j === 'number') i = j;
}
}
function skip(tokens, idx, limiter, escapes) {
while(++idx < tokens.length && tokens[idx] !== limiter)
if(escapes && tokens[idx] === '\\') ++idx;
return idx;
}
function removeDeclaration(token, tokens, idx) {
switch(token) {
case '/*':
return skip(tokens, idx, '*/');
case '//':
return skip(tokens, idx, '\n');
case ')':
tokens.splice(0, idx + 1);
return false;
}
}
function extractTopLevelFunctionNames(token, tokens, idx) {
switch(token) {
case '{':
++level;
return;
case '}':
--level;
return;
case '/*':
return skip(tokens, idx, '*/');
case '//':
return skip(tokens, idx, '\n');
case '"':
case '\'':
return skip(tokens, idx, token, true);
case 'function':
if(level === 1) {
while(++idx < tokens.length) {
token = tokens[idx];
if(token === '(')
return idx;
if(/^\s+$/.test(token))
continue;
if(token === '/*') {
idx = skip(tokens, idx, '*/');
continue;
}
if(token === '//') {
idx = skip(tokens, idx, '\n');
continue;
}
names.push(token);
return idx;
}
}
return;
}
}
function getTopLevelFunctionRefs(func) {
var tokens = tokenize(func.toString());
parse(tokens, removeDeclaration);
names = [], level = 0;
parse(tokens, extractTopLevelFunctionNames);
var code = tokens.join('') + '\nthis._refs = [' +
names.join(',') + '];';
return (new (new Function(code)))._refs;
}
return getTopLevelFunctionRefs;
})();
function testSuite() {
function testA() {
function testNested() {
}
}
// function testComment() {}
// function testGlobalFn() {}
function // comments
testB /* don't matter */
() // neither does whitespace
{
var s = "function testString() {}";
}
}
document.writeln(extractFunctions(testSuite).join('\n---\n'));
</script></pre>
Not as elegant as LISP-macros, but still nice what JAvaScript is capable of ;)
<pre>
<script type="text/javascript">
function someFn() {
/**
* Some comment
*/
function fn1() {
alert("/*This is not a comment, it's a string literal*/");
}
function // keyword
fn2 // name
(x, y) // arguments
{
/*
body
*/
}
function fn3() {
alert("this is the word function in a string literal");
}
var f = function () { // anonymous, ignore
};
}
var s = someFn.toString();
// remove inline comments
s = s.replace(/\/\/.*/g, "");
// compact all whitespace to a single space
s = s.replace(/\s{2,}/g, " ");
// remove all block comments, including those in string literals
s = s.replace(/\/\*.*?\*\//g, "");
document.writeln(s);
// remove string literals to avoid false matches with the keyword 'function'
s = s.replace(/'.*?'/g, "");
s = s.replace(/".*?"/g, "");
document.writeln(s);
// find all the function definitions
var matches = s.match(/function(.*?)\(/g);
for (var ii = 1; ii < matches.length; ++ii) {
// extract the function name
var funcName = matches[ii].replace(/function(.+)\(/, "$1");
// remove any remaining leading or trailing whitespace
funcName = funcName.replace(/\s+$|^\s+/g, "");
if (funcName === '') {
// anonymous function, discard
continue;
}
// output the results
document.writeln('[' + funcName + ']');
}
</script>
</pre>
I'm sure I missed something, but from your requirements in the original question, I think I've met the goal, including getting rid of the possibility of finding the function keyword in string literals.
One last point, I don't see any problem with mangling the string literals in the function blocks. Your requirement was to find the function names, so I didn't bother trying to preserve the function content.

Categories