9

I am needing to find the argument passed to a function from the function.

Let us suppose I have a function called foo:

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];
  bar(a - b / c);
  bar(c * a + b);
}

function bar(arg) { alert(arg) }

As it is now, of course, bar will always alert NaN.

Inside of the function bar, I want to obtain the argument in the form it was originally passed. Furthermore, I want to be able to access the values of a, b, and c from the bar function. In other words, I would like something of this nature:

bar(a - b / c);    

function bar() {
 //some magic code here
 alert(originalArg); //will alert "a - b / c"
 alert(vars.a + " " + vars.b + " " + vars.c); //will alert "3 hello 0,4"
} 

You may not think this is possible, but I know you can do weird things with Javascript. For example, you can display the code of the calling function like this:

function bar() {
  alert(bar.caller);
}

I am willing to bet a few dollars that with some sort of finagling you can get a the original form of the argument and the values of the variables in the argument.

I can easily see how you could do it if you were allowed to call bar in a form like this:

bar(function(){return a - b / c}, {a: a, b: b, c: c});

But that is too convoluted and therefore unacceptable. The way bar is called may not be changed.

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • 1
    I'm willing to bet a few points on a bounty that you can't without changing bar in some way. Before you get into bar, JS will turn the a-b/c into a real value, the way that value was calculated will be lost. [MDN on arguments](https://developer.mozilla.org/en/JavaScript/Reference/functions_and_function_scope/arguments) – Dan F Jul 16 '11 at 05:27
  • @Dan F I'll accept your challenge. I have an idea, but it involves some very ugly things and complicated parsing. – Peter Olson Jul 16 '11 at 05:40
  • You're on - I'll bounty this tomorrow for you :-) I gave this a +1 though, as it's an interesting question – Dan F Jul 16 '11 at 05:42
  • 1
    No, it's true. The compiler will turn `a - b * c` or whatever other ___expression___ into the ___actual value___ before passing it to the next function down. – jcolebrand Jul 16 '11 at 06:30
  • Would the downvoter like to comment? – Peter Olson Jul 16 '11 at 16:18
  • @Dan F I've posted my solution. I don't know if it completely qualifies, but I thought you'd at least like to check it out. – Peter Olson Jul 16 '11 at 18:48
  • I don't even know what he is asking... Too complex... – Derek 朕會功夫 Jul 21 '11 at 23:26

6 Answers6

6

First of all — weird question. It would help a lot to know the context of why you are asking, because obviously Javascript doesn't have exactly what you are asking. (For example, is it something specifically to do with math expressions? Does the expression have to be executed prior to calling the function, as in your example?)

Javascript does not have:

  • named parameters
  • first-class/dynamic expressions

Javascript does have:

  • dynamic objects
  • first-class functions
  • closures
  • eval

Dynamic objects

Since this was your own stated option at the end, that you declared unacceptable, it sounds like it won't help you much. In any case, since you want the expression and the values, just passing an object of the values alone isn't going to help you.

First-class functions

As you stated, you do have access to Function.caller, although that just outputs a String representation of the whole function. It is also non-standard.

You also have arguments, which is an Array of the arguments that were passed in, not named. In the case of your existing code, obviously, it will be the executed result of the expression. One convenient use of arguments is not specifying any parameters in the function signature and then iterating through the array as an open-ended list.

Closures

This is also a part of your unacceptable solution at the end, but could possibly the closest to a working solution for you, though you'd have to be flexible on the way bar is called or where it is defined.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  var bar = function(arg) {
    alert(arg); // NaN
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4"
  }

  bar(a - b / c);
  bar(c * a + b);
}

eval

Using eval and closures, you might get close to what you want.

The primary thing to remember is that in your code, a - b / c is an expression. That is not the argument, the result of the expression is the argument. There is nothing besides a toString on the function itself that will alert "a - b / c".

With eval (Disclaimer: this is very hacky and not recommended! You've been warned.), you could actually pass a String as an argument, and using a closure which is bound to the space, alert what you want.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  function hackyUglyEvalAndOutput(callback, expression) {
    alert(expression); //will alert "a - b / c" (duh)
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4" (because of the closure)
    callback(eval(expression)); // "NaN"
  }
  hackyUglyEvalAndOutput(bar, "a - b / c");
  hackyUglyEvalAndOutput(bar, "c * a + b");
}

function bar(arg) { alert(arg); }

You could similarly parse caller to find calls to the function and see what was passed into the argument list (I did get it to do this) but you will have no way of distinguishing which one made the call if there is more than one. From your example, that would be crucial to accomplish what you want to do.

Again — expressions are just that and nothing more; they are not arguments.

P.S. The second call to bar will actually output NaNhello :)

Nicole
  • 32,841
  • 11
  • 75
  • 101
  • +1, I see that you have put quite a bit of effort into this answer. Unfortunately, it's not quite the one I wanted. I would tell you why I am trying to do this, but you would probably think I am more crazy than you already do. – Peter Olson Jul 16 '11 at 16:38
  • Oh, and parsing caller was exactly my idea to solve it. I thought of a way to find out, most of the time, which one was the caller. – Peter Olson Jul 16 '11 at 16:41
  • Thank you for the acknowledgment, and I realize it's probably not the one you wanted -- really, I just hoped to give you more evidence why it's really not going to be possible -- unless you pretty much write a Javascript parser. :) – Nicole Jul 16 '11 at 17:03
  • I've posted a solution of my own. It does indeed involve parsing Javascript, but you might be interested anyway. – Peter Olson Jul 16 '11 at 18:50
  • Doesn't this count as a poor-man's JavaScript named parameter? foo({ named:1, parameter:2 }) – Joe Nelson Jul 24 '11 at 15:04
4

Ok, I did it! (sort of) What you see below is the most naive, buggy, hideous, and hacky code I have ever written. This is the first time I have ever really used regex, and I'm not very good at making parser code.

Here we go:

    function foo () {
        var a = 3;
        var b = "hello";
        var c = [0, 4];
        bar(a - b / c); 
        bar(c * a + b);
    };

    var callTracker = {};
    function bar() {
        var caller = bar.caller.toString();
        callTracker[caller] !== undefined ? callTracker[caller]++ : callTracker[caller] = 0;
        function findCall(str) {
            return str.search(/bar\((\S*\s*)*\);/);
        }

        //Step 1: Get the orginal form of the argument
        var callers = [caller];
        var index = 0;
        var len;
        while (true) {
            len = callers.length - 1;
            index = findCall(callers[len]);
            if (index === -1) break;
            callers.push(callers[len].substring(index + 1));
        }
        var callIndex = callTracker[caller] % len;
        var thisCall = callers[callIndex];
        var argument = thisCall.substring(findCall(thisCall));
        argument = argument.slice(4, argument.search(/\);/));
        alert(argument);

        //Step 2: Get the values of the variables
        caller = caller.slice(caller.search(/\{/) + 1, -1);
        var lines = caller.split(";");
        for (var i = 0; i < lines.length; i++) {
            lines[i] += ";";
            if (findCall(lines[i]) === -1) {
                eval(lines[i]);
            }
        }
        var vars = argument.match(/\w/g);
        for (var i in vars) {
            if (vars.hasOwnProperty(i)) {
                alert(eval(vars[i]));
            }
        }
    }

    foo();

I can forsee quite a few situations where this will break, but I'm too lighthearted to test them out. Just a couple examples: if the code contains a string literal with a semicolon in the middle, it will break, or if somebody relies on automatic semicolon insertion, it will break. If there is a conditional return statement between the two calls to bar, it will break, due to my extremely naive parsing.

But it does work for this example. If used in a real project, I think it would actually work most of the time, but there could end up being some really weird bugs.

If you want to play with it yourself, here it is on JsFiddle.

The final takeaway, I think, is never underestimate the power of a program that can read (and even change) itself.

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • My Mind Is Blown. I just learnt a thing or two about JS. [MDN on caller](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/caller) has a few warnings too, but, *wow* – Dan F Jul 16 '11 at 21:08
3

Personally I was thinking of generating an exception and then follow the stacktrace but that would only work on firefox.. Fortunately someone wrote a stacktrace library which would always indicate which function is calling so that you can parse out the exact line that is calling.

https://github.com/eriwen/javascript-stacktrace/blob/master/stacktrace.js

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
tomdemuyt
  • 4,572
  • 2
  • 31
  • 60
3

Your question is misguided and sick and...I love it! Here is a short solution which I will explain.

function foo() {
    var a = 3, b = "hello", c = [0, 4];
    bar(a - b / c); 
}

function bar(n) {
    alert([n, a, b, c, eval(n)].join('   '));
}

function meld(f, g) {
    f = f.toString(); g = g.toString();
    f = f.replace(/function\s+\w+/, 'function ');

    var n = g.match(/function\s+(\w+)/)[1];
    f = f.replace(new RegExp(n + '\\((.*?)\\)', 'g'), n + '("$1")');

    var i = f.indexOf('{') + 1;
    f = f.slice(0, i) + g + f.slice(i);

    eval('var ret = ' + f);
    return ret;
}

foo = meld(foo, bar);
foo();

The key is the meld(f, g) function which returns a new anonymous function that acts just like f while calling and exposing its internals to a copy of g. You see, the original g is in a bad position to see anything about f -- at best it can use g.caller and tons of regular expressions.

Here is the pseudo code for meld. First make f an anonymous function, suitable for evaluating and assigning to a variable later on; for instance function foo() { becomes function() {. Next find g's real name and quote any arguments passed to it from new f. Next, insert a copy of g inside new f. It will mask the global name and it will have access to f's local variables as if they were its own. Finally, evaluate this anonymous function and return it.

So how do we use meld? Just replace foo with meld(foo, bar) and then use foo as you normally would.

OK, now for the limitations. I didn't want to spend lots of effort refining the regex inside meld to quote g's argument(s) against all possibilities. This would just have been a distraction from the concept of the solution as a whole. You'll need to change it to properly handle more than one argument and escape any which themselves have quotes or parentheses.

Joe Nelson
  • 549
  • 5
  • 12
  • That's a fairly elegant way to do it, as far as this sort of abuse of Javascript goes. – Peter Olson Jul 24 '11 at 03:02
  • Your regex solution does have the benefit that you needn't meld things ahead of time. The callee function can itself deal with any invocation. Great mind-bending stuff. – Joe Nelson Jul 24 '11 at 15:12
2

My input to the topic:

http://jsfiddle.net/eWBpF/2/

function foo () {
    var a = 3;
    var b = "hello";
    var c = [5, 2];
    var d = new Date();
    bar(a - b / c);
    bar(c * a + b - d);
};

function bar(args)
{
    var caller = bar.caller.toString();

    if( typeof window.bar_call == 'undefined' )
    {
        window.bar_call = 0;
    }
    else
    {
        window.bar_call++;
    }

    var indexes = caller.match(/var \S* = [^;]+;/g);
    var varValues = [];
    for( var i=0; i<indexes.length; i++ )
    {
        var tmpVal = indexes[i].substring(indexes[i].search(/=/));
        tmpVal = tmpVal.substring(2,tmpVal.length-1);

        varValues.push(tmpVal)
    }
    var variables = varValues.join("  ");

    var barCalls = caller.match(/bar\([^;]+\);/g);

    var call = barCalls[window.bar_call];
    call = call.substring(4, call.length-2);

    alert(call + "\n\n" + variables);
}

foo();
Joe Nelson
  • 549
  • 5
  • 12
Daniel Gruszczyk
  • 5,379
  • 8
  • 47
  • 86
1

This would be neat, but it is not possible. The closest thing you can get is arguments, which is simply an array of all the arguments to the current function. You could enumerate this list and derive the types and values, and get the length so you know how many arguments - but the name and the operators will be lost.

See http://www.devguru.com/technologies/ecmascript/quickref/arguments.html

Josh
  • 6,155
  • 2
  • 22
  • 26