0

I'm creating a sort of dsl that lies inside of javascript - and for it to be as clean as possible, it is important that I don't have to specifically pass things to it, it can just pick them up anything in the current scope by name - but I'm wondering if it is actually possible. If I'm in a function, is there any way to access local variables from the caller function?

I know you aren't supposed to be able to do this, but it's all out there on the stack, a debugger can do it, and there doesn't seem to be a lot of restrictions on what you can to in javascript, so I'm wondering if there's some obscure way, even if it's intensely hacky.

eg

function dsl( someString )
{
    // can I access bob and cindy here?
}

function someRealFunction()
{
    var bob = [1,2,3,4];
    var cindy = [1,2,3,8];
    return dsl("difference bob and cindy");
}

This is slightly different from the question about passing scope to another function, because I don't want to pass scope - I just want to implicitly pick up the scope which is there. A debugger can do it, so fundamentally the information IS actually there - the question is whether it can be accessed.

Darren Oakey
  • 2,894
  • 3
  • 29
  • 55
  • Possible duplicate of [JavaScript pass scope to another function](https://stackoverflow.com/questions/6348852/javascript-pass-scope-to-another-function) – Mark Jun 27 '18 at 06:10
  • `to be as clean as possible, it is important that I don't have to specifically pass things to it` Even if it were possible, that'd probably be the *opposite* of clean - explicitly telling functions exactly (and only) what they need to to run is a good thing. Pure functions are great – CertainPerformance Jun 27 '18 at 06:11
  • I don't know about you, but I reckon dsl("difference bob and cindy") is cleaner than the alternative - dsl( "difference bob and cindy", {bob:bob, cindy:cindy})? – Darren Oakey Jun 27 '18 at 06:34

3 Answers3

1

Obscure way - you say;

function dsl( someString )
{
    // can I access bob and cindy here?
    eBob=arguments.callee.caller.toString().split("\n")[2];
    eCindy=arguments.callee.caller.toString().split("\n")[3];
    
    eval(eBob);
    eval(eCindy);
    console.log(arguments.callee.caller)
    console.log("bob", bob);
    console.log("cindy", cindy);
    

}

function someRealFunction()
{
    var bob = [1,2,3,4];
    var cindy = [1,2,3,8];
    return dsl("difference bob and cindy");
}

someRealFunction();

For the theoretical reasoning and security considerations only. Obviously, don't use this in anything resembling production code.

visibleman
  • 3,175
  • 1
  • 14
  • 27
  • I was hoping it would work with any variable and any function :) – Darren Oakey Jun 27 '18 at 06:32
  • Using the arguments.callee.caller will work with any function, obviously, you would have to improve the parsing of what variables to extract. – visibleman Jun 27 '18 at 06:33
  • 1
    that only works if the variables aren't... well... variable. If bob = somethingElse+1 - all bets are off – Darren Oakey Jun 27 '18 at 06:54
  • Well, you can't expect to abuse the language specification, and not be punished for it. :-). It all sounds a bit like an xy-problem, why don't you describe what is the you are trying to solve. Typically you would solve this by creating and object and pass it to the function. – visibleman Jun 27 '18 at 07:02
  • ok - lets suppose I want to implement the equivalent of the new ecmascript backticks... - ie I want to be able to create a function called render such that I can say render("the total is ${total}") - and it will just magically pick up total from the current scope – Darren Oakey Jun 27 '18 at 07:05
  • I mean there are Template literals in ES2015 and forward [ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals ] but that is as close as you are gonna get, I think – visibleman Jun 27 '18 at 07:18
1

A callback/closure pattern might do what you want:

function dsl( someString, cb )
{
    // can I access bob and cindy here?
    cb();
}

function someRealFunction()
{
    var bob = [1,2,3,4];
    var cindy = [1,2,3,8];
    return dsl("difference bob and cindy", cb);

    function cb() {
      console.log(diff(bob, cindy));
    }
}
  • this is all for code readability - imagine for example javascript didn't have `s, and you wanted that functionality. You want to be able to say render("this is a test - ${someVar}, ${someOtherVar}") ... you don't want to say render("this is a test - ${someVar}, ${someOtherVar}", {someVar:someVar, someOtherVar:someOtherVar} ) - but even that is much cleaner than having a callback function – Darren Oakey Jun 27 '18 at 06:58
  • Approaches to template literals which can be passed around as strings and then interpreted in template literal-fashion at run-time have been discussed elsewhere here on SO. But the values for the data have to come from somewhere. They cannot just come from the insides of some random other function. –  Jun 27 '18 at 19:06
0

Disclaimer: Of course, the correct way here is to change your paradigm to match the language you are working in and use something like the callback pattern suggested by @torazaburo, or the solutions in the suggested duplicate by @Mark M.

But since you are specifically asking for 'some obscure way, even if it's intensely hacky', here is obscure hacky way number two. Posting this as a separate answer as it is a different technique.

You can extend the string class with a getter that gets the evaluated value of a name. You need to create the prototype in the calling function, or the getter won't have the correct scope.

function dsl( someString )
{
    // can I access bob and cindy here?
  console.log(someString.handKeysToKingdom("cindy"));
  console.log(someString.handKeysToKingdom("bob"));
}

function someRealFunction()
{
    String.prototype.handKeysToKingdom=function(name){return(eval(name))};

    var bob = [1,2,3,4];
    var cindy = [1,2,3,8];

    return dsl("difference bob and cindy");
}

someRealFunction();

The sort of obivous non-hacky way to do this, that I think most JS devs would agree on looks pretty clean.

function dsl( someString, params )
{
    // can I access bob and cindy here?
    console.log(params['bob']);
    console.log(params.cindy);
    return 
}

function someRealFunction()
{
  
    var dslParams={};
    
    dslParams.bob= [1,2,3,4];
    dslParams.cindy = [1,2,3,8];
 
    return dsl("difference bob and cindy",dslParams);
}

someRealFunction();

Using similar logic you can somewhat hackily abuse the fact that functions are objects, and pass the parameters by setting them on the dsl object.

function dsl( someString )
{
    // can I access bob and cindy here?
    console.log(dsl['bob']);
    console.log(dsl.cindy);
    return 
}

function someRealFunction()
{

    dsl.bob= [1,2,3,4];
    dsl.cindy = [1,2,3,8];
 
    return dsl("difference bob and cindy");
}

someRealFunction();
visibleman
  • 3,175
  • 1
  • 14
  • 27
  • my aim is to make something beautiful and clean - so obviously having that line in every function isn't going to cut it.... but that said - _awesome_ - I love the hackiness of it :) – Darren Oakey Jun 29 '18 at 00:51
  • @DarrenOakey I added a few more alternatives, but then that is enough procrastination of $work for one day. – visibleman Jun 29 '18 at 01:35