1

This is continuation of this question with more difficult case. Suppose I want to call string function with 2 parameters e.g.

console.log(
  "truefalse".replace("true",1)
)

In first step I reduce characters set to jsfuck convention where we have 6 available characters: []()!+ and with a-z letters and numbers surrounded by " chars - JS strings (which are easy to convert to those 6 chars):

console.log(
  "truefalse"["replace"]("true","1")
)

The problem here was comma (forbidden character) but we can overcome this problem by use following clever technique discovered by trincot:

console.log(
  ["true"]["concat"]("1")["reduce"](""["replace"]["bind"]("truefalse"))
)

But the new question arise:

It is possible to call sequence of functions with 2 (or more) parameters without nesting them (which is imposed by above technique) but in "flow" way where we call next function in right side eg.: "truefalse".replace("true",1).replace("false",0).. (without using 'eval' like solution where string is interpreted as code) ? (for function with one parameter it is possible e.g.: "truefalse"["replace"]("true")["replace"]("false") )

j08691
  • 204,283
  • 31
  • 260
  • 272
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • Uh, `reduce` is a rather weird usage here (and strictly speaking wrong, as it calls the function with [four arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Syntax)). Why not just call [`["apply"]`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply)? – Bergi Aug 26 '20 at 19:26
  • @Bergi can you develop your concept (e.g. as answer in source question)? – Kamil Kiełczewski Aug 26 '20 at 19:28
  • It seemed too trivial for an answer… There's no problem with the letter set `aply`, no? – Bergi Aug 26 '20 at 19:31
  • @Bergi you can use string `"apply"` (a-z letters and numbers in strings are allowed) – Kamil Kiełczewski Aug 26 '20 at 19:32
  • @Bergi the 1st arg of apply should be valid `this`. It's a bit hard to wrap around – hackape Aug 26 '20 at 19:32
  • @hackape The `this` argument for `apply` when calling it on a method is the original receiver, in your case the string `"truefalse"`. So just `""["replace"]["apply"]("truefalse",["true"]["concat"](["1"]))`. – Bergi Aug 26 '20 at 19:35
  • @hackape Oh wait… There's another comma in there. – Bergi Aug 26 '20 at 19:35
  • 1
    @Taplar Why would we remove that? [JSFuck](http://www.jsfuck.com/) is what the language subset is called. – Bergi Aug 26 '20 at 19:37

1 Answers1

2

Yes, it is possible.

So we start with the expression that omits the comma, and only consists of string literals and the JSF characters:

["true"]["concat"]("1")["reduce"](""["replace"]["bind"]("truefalse"))

For a moment, I will phrase this expression using the more readable dot notation, and go back to the comma separator for array literals:

["true", "1"].reduce("".replace.bind("truefalse"))

This has the input of the replacement, i.e. "truefalse", sitting at the end. The parameters, on the other hand, are located at the left, i.e. "true" and "1". We could try to make "truefalse" also an argument, so that we could move it to the left.

For that purpose we can use "".replace.apply instead of "".replace as callback to reduce. The first argument of apply is the this-binding for the replace call. The second argument should be the array of arguments to pass to replace, so that is the array we currently have at the left.

And then the apply method itself should also get a this-binding. We get this expression:

console.log(
    ["truefalse", ["true", "1"]].reduce("".replace.apply.bind("".replace))
);

NB: "".replace.apply could reference any other function instead of replace, as long as it is a function. We just need a way to reference the Function.prototype.apply function.

So, we have succeeded to move the "truefalse" expression more to the front. But it really should not sit in an array literal if we want to achieve non-nested chaining.

Here we can use a "feature" of the split method: if you don't pass any argument, it returns an array with the original string. Exactly what we need.

So:

console.log(
    "truefalse".split().concat([["true", "1"]]).reduce("".replace.apply.bind("".replace))
);

Now we can chain!

So, to finalise, here is the same expression with the dots and commas removed:

console.log(
  "truefalse"["split"]()["concat"]([["true"]["concat"]("1")])
    ["reduce"](""["replace"]["apply"]["bind"](""["replace"]))
);

...and to chain, you just continue the expression with ["split"]() ...etc.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Wow - this is really impressive - I thought that it is impossible (!!!). But there is still one problem with [original question](https://stackoverflow.com/q/63601330/860099) - the regular expression part: `"truefalse"["replace"](/e/g,"E")` - some person suggest to use `new Regexp...` but actually I realise that this probably need `eval` to run... If you think it is good, I can remove regexp part from that question and create new question... – Kamil Kiełczewski Aug 26 '20 at 19:50
  • It is indeed better to only ask one question at a time. If you ask a new question for it, I'll have a sleepless night thinking about it ;-) – trincot Aug 26 '20 at 19:52
  • here is [question](https://stackoverflow.com/q/63605144/860099) – Kamil Kiełczewski Aug 26 '20 at 20:17
  • may be [this question](https://stackoverflow.com/q/63631908/860099) will be also interesting for you – Kamil Kiełczewski Aug 28 '20 at 10:22