Extreme JavaScript Type Coercion
Tags: javascript, computerscience, programming, algorithms
Published: 2023-02-14 00:40:58

## Introduction

Last week I stumbled upon this silly bit of JavaScript:
```javascript
console.log(('b' + 'a' + + 'a' + 'a').toLowerCase());
// RETURNS: banana
```
I had seen similar strange code snippets before, but had not felt the urge to explore the mechanics of what was happening until I came across a wonderful YouTube video by Low Byte Productions [3]. In the video titled "JavaScript Is Weird EXTREME EDITION", we are shown a short script which converts JavaScript into JavaScript composed exclusively from the characters ({[/>+!-=\]}). The new and comically unreadable code would of course compile exactly the same as the original. This post recaps a weekend spent looking into JavaScript type coercion and reproducing Low Byte Production's extremely strange JavaScript compiler.

## Type Coercion

### Definition

In some programming languages, the combination of different data types is not allowed. For example, in Python:

```python
print(1 + "1")
# RETURNS: TypeError: can only concatenate str (not "int") to str
```

JavaScript is more friendly than Python in that it will make an attempt to combine different data types. _Type coercion_ is the automatic or implicit conversion of values from one data type to another [1]. In JavaScript, the same code yields a result:

```javascript
console.log(1 + "1");
// RETURNS: 11

console.log(1 + 1 + "1");
// RETURNS: 21

console.log("1" + 1 + 1);
// RETURNS: 111
```

In the second example, JavaScript adds the first two 1's to 2 since they are both of type `Number`, and then converts this to a string before adding the final `"1"` of type `String`.

This highlights the importance of the difference between `==` and `===` in JavaScript. The `==` operator uses type coercion, whereas `===` only matches equal types.

```javascript
console.log(1 == "1");
// RETURNS: true

console.log(1 === "1");
// RETURNS: false
```

### The Unary Plus Operator

The _unary plus_ operator `+` precedes its operand and will attempt to convert the operand into a number before returning the operand. It can convert strings that represent integers or floats to numbers, and will also convert the values `true`, `false`, and `null` [2].

```javascript
const x = 1;
const y = -1;

console.log(+x);
// RETURNS: 1

console.log(+y);
// RETURNS: -1

console.log(+true);
// RETURNS: 1

console.log(+false);
// RETURNS: 0

console.log(+'');
// RETURNS: 0

console.log(+'2');
// RETURNS: 2

console.log(+'3.1415');
// RETURNS: 3.1415

console.log(+'hello');
// RETURNS: NaN
```

### Strange Type Coercion

Here are some other examples of type coercion that may be less intuitive:

```javascript
console.log(+[]);
// RETURNS: 0

console.log(![]);
// RETURNS: false

console.log(!![]);
// RETURNS: true

console.log(+{});
// RETURNS: NaN

console.log(+{}+[]);
// RETURNS: 'NaN'

console.log([]+[]);
// RETURNS: '' (empty string)
```
Notice that adding `+[]` to an expression will convert it directly to a string. 

Since the unary plus of a non-numeric string returns `NaN`, we can write some funny JavaScript that use type coercion. What will happen when we try to evaluate `'ba' + (+'a')`? This becomes `'ba' + NaN` since `'a'` is non-numeric. Now since JavaScript is adding a type `number` to a type `string` (yes, `typeof NaN === number`), it will convert the `NaN` to a string and return `'baNaN'`. Now we can see why the first code snippet logs `banana`. The value of the string with the unary operator does not need to be `a`, it just needs to be non-numeric:

```javascript
console.log(('b' + 'a' + + 'pomegranate' + 'a').toLowerCase());
// RETURNS: banana
```

## Numbers and Letters With Just ([!+])

Since `+true` evaluates to 1 and `!![]` evaluates to `true`, we can create the positive integers:

```javascript
console.log(+!![]);
// RETURNS: 1

console.log(+!![] + +!![]);
// RETURNS: 2

console.log(+!![] + +!![] + +!![]);
// RETURNS: 3
```

Programmatically:
```javascript
const zero = '+[]';
const one = '+!![]';

function getNum(n) {
    if (n == 0) return zero;
    return Array(n).fill(one).join(' + ');
};

console.log(getNum(7));
// RETURNS: +!![] + +!![] + +!![] + +!![] + +!![] + +!![] + +!![]
```
This repeated string evaluates to 7 in JavaScript. Try it in the console! Now that we have all the numbers, let's use them to obtain some letters.

Recall that JavaScript's type coercion gives `!![]+[] === true`, where `true` is a string because of the `+[]`. We can access the letters of `true` now with string indexing:
```javascript
console.log((!![]+[])[+[]]);
// RETURNS: t
console.log((!![]+[])[+!![]]);
// RETURNS: r
console.log((!![]+[])[+!![] + +!![]]);
// RETURNS: u
console.log((!![]+[])[+!![] + +!![] + +!![]]);
// RETURNS: e
```  

Note that we could have used the `getNum` function earlier, e.g. use `getNum(3)` in place of `+!![] + +!![] + +!![]` for the last letter, but here we can see that the characters are truly derived from just the set ([!+]).

## Collecting Characters

Eventually, we would like to write _any_ character in terms of our symbol set. The final method to achieve this will require us to have access to `constructor`, `toString`, `return`, `escape`, `charCodeAt`, `\`, and the space character. This means we need to obtain the following characters using only ({[/>+!-=\]}): 

```javascript
fromChadetSingcup[space][backslash] 
```

### Easy Words

The first round of characters will be just like the example above using the following strings derived from JavaScript type coercion:

* `+{}+[]` yields the string `NaN`
* `{}+[]` yields the string `[object Object]`
* `!![]+[]` yields the string `true`
* `![]+[]` yields the string `false`
* `(+!![]/+[])+[]` yields the string `Infinity` 

Here are the characters from `Infinity` using string interpolation with our `getNum` function: 

```javascript
// +!![]/+[] => Infinity
console.log(`((+!![]/+[])+[])[${getNum(3)}]`);
// RETURNS: i
console.log(`((+!![]/+[])+[])[${getNum(4)}]`);
// RETURNS: n
```

We will store the string value for all of these characters using the `chars` object:

```javascript
const zero = '+[]';
const one = '+!![]';

function getNum(n) {
    if (n == 0) return zero;
    return Array(n).fill(one).join(' + ');
};

// WANT: fromChadetSingcup[space][backslash]

const chars = {};

// +{} => NaN
chars.a = `(+{}+[])[${getNum(1)}]`;

// {}+[] => '[object Object]'
chars.b = `({}+[])[${getNum(2)}]`;
chars.o = `({}+[])[${getNum(1)}]`;
chars.e = `({}+[])[${getNum(4)}]`;
chars.c = `({}+[])[${getNum(5)}]`;
chars.t = `({}+[])[${getNum(6)}]`;
chars[' '] = `({}+[])[${getNum(7)}]`;

// ![] => false
chars.f = `(![]+[])[${getNum(0)}]`;
chars.s = `(![]+[])[${getNum(3)}]`;

// !![] => true
chars.r = `(!![]+[])[${getNum(1)}]`;
chars.u = `(!![]+[])[${getNum(2)}]`;

// +!![]/+[] => Infinity
chars.i = `((+!![]/+[])+[])[${getNum(3)}]`;
chars.n = `((+!![]/+[])+[])[${getNum(4)}]`;
``` 

### Complex Words

#### Constructors

Calling the constructor method on a JavaScript variable will return the constructor of that type. If we attach our friend `+[]`, to it, we get the constructor name as a string:

```javascript
console.log((1)['constructor']+[]);
// RETURNS: function Number() { [native code] }
```

The next set of characters will get a string by accessing this `constructor` method. We have all of the characters necessary to make the word `constructor`, so define the function `convertString` which converts the input string to our symbol set. Note that this will only work for strings whose characters are already defined in `chars`.

```javascript
function convertString(string) {
    return string.split('').map(char => chars[char]).join(' + ');
}
```

We get three letters from constructors, two from strings and one from regexs:

```javascript
// ([]+[])['constructor']+[] => 'function String() { [native code] }'
chars.S = `([]+([]+[])[${convertString('constructor')}])[${getNum(9)}]`;
chars.g = `([]+([]+[])[${convertString('constructor')}])[${getNum(14)}]`;

// (/-/)['constructor']+[] => 'function RegExp() { [native code] }
chars.p = `([]+(/-/)[${convertString('constructor')}])[${getNum(14)}]`;
```

#### toString(n)

The next three letters use a clever trick with the `toString` method of numbers. The method takes a target base as parameter and will convert the integer it is called on to that base as a string. 

* 13 in base 14 is d 
* 17 in base 18 is h
* 22 in base 23 is m

```javascript
// convert base 10 ints to hexadecimal to get letters
chars.d = `(${getNum(13)})[${convertString('toString')}](${getNum(14)})`;
chars.h = `(${getNum(17)})[${convertString('toString')}](${getNum(18)})`;
chars.m = `(${getNum(22)})[${convertString('toString')}](${getNum(23)})`;
```

#### Capital C

The last letter, C, is the most complex. Before we can get C, we need the backslash character. We need to store `\\` in `chars`, since, when evaluated, the first backslash escapes. This is achieved by converting a stringified regex with four backslashes to a string and accessing index 1:

```javascript
// regex
chars['\\'] = `(/\\\\/+[])[${getNum(1)}]`;
```

The JavaScript `escape` function will replace all characters with ASCII escape sequences [4]. To get C, we will use the fact that `escape('\\') === '%5C'`. We can get the escape function using the function constructor like so:

```javascript
console.log((()=>{})['constructor']('return escape')());
// RETURNS: [Function: escape]
console.log((()=>{})['constructor']('return escape')()('\\'));
// RETURNS: %5C
```

So capital C in our code is

```javascript
chars.C = `(()=>{})[${convertString('constructor')}](${convertString('return escape')})()(${chars['\\']})[${getNum(2)}]`
``` 

## Write ANY Character With Just ({[/>+!-=\]})

Now we are equipped to produce any other character with our strange syntax. We will edit the `convertString` function to handle inputs that are not already in the map. If the input character is not in `chars`, get the character code of the single value string with `charCodeAt(0)`. Then return the string constructor called with the `fromCharCode(charCode)` method.

```javascript
function convertString(string) {
    return string.split('').map(char => {
        if (!(char in chars)) {
            const charCode = char.charCodeAt(0);
            return `([]+[])[${convertString('constructor')}][${convertString('fromCharCode')}](${getNum(charCode)})`;
        }
        return chars[char];
    }).join(' + ');
}
```

Now we can convert any string into a string containing only ({[/>+!-=\]}). The last code we'll add at the ability to execute our 'symbol code'. Here is the result:

```javascript
const zero = '+[]';
const one = '+!![]';

function getNum(n) {
    if (n == 0) return zero;
    return Array(n).fill(one).join(' + ');
};

const chars= {};

function convertString(string) {
    return string.split('').map(char => {
        if (!(char in chars)) {
            const charCode = char.charCodeAt(0);
            return `([]+[])[${convertString('constructor')}][${convertString('fromCharCode')}](${getNum(charCode)})`;
        }
        return chars[char];
    }).join(' + ');
}

// +{} => NaN
chars.a = `(+{}+[])[${getNum(1)}]`;

// {}+[] => '[object Object]'
chars.b = `({}+[])[${getNum(2)}]`;
chars.o = `({}+[])[${getNum(1)}]`;
chars.e = `({}+[])[${getNum(4)}]`;
chars.c = `({}+[])[${getNum(5)}]`;
chars.t = `({}+[])[${getNum(6)}]`;
chars[' '] = `({}+[])[${getNum(7)}]`;

// ![] => false
chars.f = `(![]+[])[${getNum(0)}]`;
chars.s = `(![]+[])[${getNum(3)}]`;

// !![] => true
chars.r = `(!![]+[])[${getNum(1)}]`;
chars.u = `(!![]+[])[${getNum(2)}]`;

// +!![]/+[] => Infinity
chars.i = `((+!![]/+[])+[])[${getNum(3)}]`;
chars.n = `((+!![]/+[])+[])[${getNum(4)}]`;

// ([]+[])['constructor']+[] => 'function String() { [native code] }'
chars.S = `([]+([]+[])[${convertString('constructor')}])[${getNum(9)}]`;
chars.g = `([]+([]+[])[${convertString('constructor')}])[${getNum(14)}]`;

// (/-/)['constructor']+[] => 'function RegExp() { [native code] }
chars.p = `([]+(/-/)[${convertString('constructor')}])[${getNum(14)}]`;

// regex
chars['\\'] = `(/\\\\/+[])[${getNum(1)}]`;

// convert base 10 ints to hexadecimal to get letters
chars.d = `(${getNum(13)})[${convertString('toString')}](${getNum(14)})`;
chars.h = `(${getNum(17)})[${convertString('toString')}](${getNum(18)})`;
chars.m = `(${getNum(22)})[${convertString('toString')}](${getNum(23)})`;

// escape function
chars.C = `(()=>{})[${convertString('constructor')}](${convertString('return escape')})()(${chars['\\']})[${getNum(2)}]`

const compile = code => `(()=>{})[${convertString('constructor')}](${convertString(code)})()`;

console.log(compile('console.log("Hello, World!");'));
```

Whatever JavaScript code is passed as a string on the last line will be logged to the console as valid JavaScript using only ({[/>+!-=\]})!

I encourage you to paste the above code in your dev tools and see the output for yourself. Copy that output and run it as JavaScript to see `Hello, World!` printed to the console.

_To reiterate, I did not come up with this code. I followed the video by Low Byte Productions [3] and read MDN documentation [1-2, 4] to understand the mechanics of the code and be able to reproduce and explain the code._

_References:_

[1] MDN - Type Coercion - https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion
[2] MDN - Unary Plus - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus
[3] Low Byte Productions - JavaScript Is Weird (EXTREME EDITION) - https://www.youtube.com/watch?v=sRWE5tnaxlI&t=50s  
[4] MDN - escape() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape