2017 @phenomnominal

I'm Craig

I do JavaScript at

You can find me on the Twitters

@phenomnominal

2017 @phenomnominal

First, a STORY...

2017 @phenomnominal

you're a wizard!

nz.js(con) attendee

2017 @phenomnominal

You went to hogwarts!

2017 @phenomnominal

YOU KNOW Dumbledore!

2017 @phenomnominal

AND HARRY POTTER!

2017 @phenomnominal

But there's a problem!

2017 @phenomnominal

VOLDEMORT HAS PUT A CURSE ON HARRY!

2017 @phenomnominal

(this is 100% canon, don't look it up)

2017 @phenomnominal

But! Harry speaks Parseltongue.

2017 @phenomnominal

Which just so happens to be a turing-complete programming language!

(again, there's definitely no need to look this up)

2017 @phenomnominal

PARSELTONGUE 🐍🐍🐍:

Parseltongue is a pretty simple language, but very hard to read!

Variables:

sssHelloWorld <~ 'hello world'
sssFive <~ 5
sssBool <~ true
sssMultiply [sssA, sssB, sssC]
    <~ sssA * sssB * sssC

Functions:

Calls:

sssProduct <~ sssMultiply <~ [3, 4, 5]
var helloWorld = 'hello world';
var five = 5;
var bool = true;
var multiply = function (a, b, c) {
    return a * b * c;
}
var product = multiply(3, 4, 5);

Parseltongue

JavaScript

2017 @phenomnominal

PARSELTONGUE 🐍🐍🐍:

Control flow:

ss something
    sssDoSomething
ssssss somethingElse
    sssDoSomethingElse
ssss
    sssDoDefault
if (something) {
    doSomething();
} else if (somethingElse) {
    doSomethingElse();
} else {
    doDefault();
}

Parseltongue

JavaScript

Loops:

sssStart <- 5
sssEnd <~ 10
sssss sssI <~ sssStart ~> sssEnd
    sssDoSomething sssI
for (var i = start; i < end; i++) {
    doSomething(i);
}

Thankfully, Parseltongue has a type system that is identical to JavaScript

2017 @phenomnominal

PARSELTONGUE 🐍🐍🐍:

Harry uses Parseltongue to write complex spells to try to break Voldemort's curse:

sssSpell <~ 'Expecto Patronum'

sssMagic [sssSpell, sssIntensity]
    sssIntense <~ ''
    sssss sssI <~ 0 ~> sssIntensity
       sssIntense <~ sssIntense + '!'
    <~ sssSpell + sssIntense

sssss sssI <~ 0 ~> 10
    sssMagic <~ [sssSpell, sssI]
var spell = 'Expecto Patronum';

var magic = function (spell, intensity) {
    var intense = '';
    for (var i = 0; i < intensity; i++) {
       intense = intense + '!';
    }
    return spell + intense;
}

for (var i = 0; i < 10; i++) {
    magic(spell, i);
}

Parseltongue

JavaScript

2017 @phenomnominal

2017 @phenomnominal

WE NEED TO COME UP WITH A WAY TO TRANSLATE Harry's Parseltongue into JavaScript so he Can escape the internet!

2017 @phenomnominal

Parseltongue

JavaScript

???

Me too Hermione, me too.

2017 @phenomnominal

Parseltongue

JavaScript

REGEX!

2017 @phenomnominal

First ATTEMPT:

Let's just try converting a line at a time:

let script = await fs.readFileAsync(scriptPath)
let lines = script.toString().split(/\n/);
[
    'sssSpell <~ \'Expecto Patronum\'',
    '',
    'sssMagic [sssSpell, sssIntensity]',
    '    sssIntense <~ \'\'',
    '    sssss sssI <~ 0 ~> sssIntensity',
    '        sssIntense <~ sssIntense + '!'',
    '    <~ sssSpell + sssIntense',
    '',
    'sssss sssI <~ 0 ~> 10',
    '    sssMagic <~ [sssSpell, sssI]'
]

That gives us:

2017 @phenomnominal

Converting Variables:

function findVariableAssignment (line) {
    // Match on groups of 4 spaces
    // And `sss` followed by any alphabet characters (the name)
    // And `<~`
    // And then anything after it (the value)...
    let result = line.match(/^( {4})*sss([a-zA-Z]*) <~ (.*)/);
    
    // If we get a match...
    if (result) {
        let [, indents, name, value] = result;
        // Manually write out the equivalent JS...
        return `var ${lowerCaseFirst(name)} = ${value};`;
    }
    return null;
}

That's not tooooo bad...

A NaiVE Approach:

2017 @phenomnominal

Converting LOOPS:

function findWhileLoop (line) {
    // Match on groups of 4 spaces
    // And `sssss`
    // And `sss` followed by any alphabet characters (the indexer)
    // And `<~`
    // And anything (the from)
    // And `~>`
    // And anything (the to)
    let whileLoop = /^( {4})*sssss sss([a-zA-z]+) <~ (.*) ~> (.*)/;
    let result = line.match(whileLoop);

    // If we get a match...
    if (result) {
        let [, indents, indexer, from, to] = result;
        let i = lowerCaseFirst(indexer);
        // Manually write out the equivalent JS...
        return `for (var ${i} = ${from}; i < ${to}; i += 1) {`;
    }
    return null;
}

Hmmm...

A NaiVE Approach:

2017 @phenomnominal

function findFunctionDeclaration (line) {
    // Match on groups of 4 spaces
    // And `sss` followed by any alphabet characters (the name)
    // And [
    // And comma-separated `sss` followed by alphabet characters
    // And ]...
    let fdr = /^( {4})*sss([a-zA-z]+) \[(?:sss([a-zA-Z]+), )*sss([a-zA-z]+)\]/;
    let result = line.match(fdr);

    // If we get a match...
    if (result) {
        let [, indents, name, ...parameters] = result;
        // Manually write out the equivalent JS...
        let params = parameters.join(', ');
        return `function ${lowerCaseFirst(name)} (${params}) {`;
    }
    return null;
}

A NaiVE Approach:

Converting Functions:

Uh oh...

2017 @phenomnominal

2017 @phenomnominal

What are we really trying to do?

2017 @phenomnominal

Translate from one language to another

TRANSFIGURATION!

AKA TRANSPILING

2017 @phenomnominal

"Transpiling is a specific term for taking source code written in one language and transforming into another language that has a similar level of abstraction"

The first result on google says:

2017 @phenomnominal

Let's work backwards...


 magic('Wingardium leviosa');

String literal

Function call

Identifier

Expression

2017 @phenomnominal

ESTREE

{
    "body": [{
        "type": "ExpressionStatement",
        "expression": {
            "type": "CallExpression",
            "callee": {
                "type": "Identifier",
                "name": "magic"
            },
            "arguments": [{
                "type": "Literal",
                "value": "Wingardium leviosa"
            }]
        }
    }]
}

 magic('Wingardium leviosa');

JavaScript:

ESTree structure:

2017 @phenomnominal

Esprima

import { parse } from 'esprima';

let AST = parse(`
    magic('Wingardium leviosa');
`);

2017 @phenomnominal

What is an AST?

 Abstract

  disassociated from any specific instance

  the way in which linguistic elements are put together

 Syntax

 TREE

  a data structure made up of vertices and edges without having any cycles

2017 @phenomnominal

WaT.

2017 @phenomnominal

a data structure that represents the structure of code, without any actual syntax.


magic('Wingardium leviosa');

An AST is...

{
    "body": [{
        "type": "ExpressionStatement",
        "expression": {
            "type": "CallExpression",
            "callee": {
                "type": "Identifier",
                "name": "magic"
            },
            "arguments": [{
                "type": "Literal",
                "value": "Wingardium leviosa"
            }]
        }
    }]
}

Code:

AST:


(magic "Wingardium leviosa")
sssMagic <~ 'Wingardium leviosa'

2017 @phenomnominal

Which means...

2017 @phenomnominal

IF WE CAN GET FROM PARSELTONGUE TO AN AST...

2017 @phenomnominal

 THEN WE CAN GO FROM THAT AST TO JAVASCRIPT!

PARSELTONGUE

JavaScript

AST

???

2017 @phenomnominal

???

HOW DO WE DO THAT?

2017 @phenomnominal

Our naive approach was a little too naive...

2017 @phenomnominal

Let's try something a bit more robust

2017 @phenomnominal

2017 @phenomnominal

PARSELTONGUE

JavaScript

TOKENS

LEXING

???

???

AST

lexing/HEXING

Lexing is the process of breaking down source code into words that are relevant to the language, which are called tokens.

sssSpell <~ 'Expecto Patronum'

Identifier

Whitespace

Punctuator

Whitespace

String literal

2017 @phenomnominal

[
 { type: 'identifier', value: 'sssSpell', from: 0, to: 8 },
 { type: 'space', value: ' ', from: 8, to: 9 },
 { type: 'punctuator', value: '<~', from: 9, to: 11 },
 { type: 'space', value: ' ', from: 11, to: 12 },
 { type: 'stringLiteral', value: '\'Expecto Patronum\'', from: 12, to: 30 },
 { type: 'lineTerminator', value: '\n\n', from: 30, to: 32 },
 { type: 'identifier', value: 'sssMagic', from: 32, to: 40 },
 { type: 'space', value: ' ', from: 40, to: 41 },
 { type: 'punctuator', value: '[', from: 41, to: 42 },
 { type: 'identifier', value: 'sssSpell', from: 42, to: 50 },
 { type: 'punctuator', value: ',', from: 50, to: 51 },
 { type: 'space', value: ' ', from: 51, to: 52 },
 { type: 'identifier', value: 'sssIntensity', from: 52, to: 64 },
 { type: 'punctuator', value: ']', from: 64, to: 65 },
 { type: 'lineTerminator', value: '\n', from: 65, to: 66 },
 { type: 'indent', value: '    ', from: 66, to: 70 },
 { type: 'identifier', value: 'sssIntense', from: 70, to: 80 },
 { type: 'space', value: ' ', from: 80, to: 81 },
 { type: 'punctuator', value: '<~', from: 81, to: 83 },
 { type: 'space', value: ' ', from: 83, to: 84 },
 { type: 'stringLiteral', value: '\'\'', from: 84, to: 86 },
 { type: 'lineTerminator', value: '\n', from: 86, to: 87 },
 { type: 'indent', value: '    ', from: 87, to: 91 },
 { type: 'keyword', value: 'sssss', from: 91, to: 96 },
 { type: 'space', value: ' ', from: 96, to: 97 },
 { type: 'identifier', value: 'sssI', from: 97, to: 101 },
 { type: 'space', value: ' ', from: 101, to: 102 },
 { type: 'punctuator', value: '<~', from: 102, to: 104 },
 { type: 'space', value: ' ', from: 104, to: 105 },
 { type: 'numericLiteral', value: '0', from: 105, to: 106 },
 { type: 'space', value: ' ', from: 106, to: 107 },
 // ...
sssSpell <~ 'Expecto Patronum'

sssMagic [sssSpell, sssIntensity]
    sssIntense <~ ''
    sssss sssI <~ 0 ~> sssIntensity
       sssIntense <~ sssIntense + '!'
    <~ sssSpell + sssIntense

sssss sssI <~ 0 ~> 10
    sssMagic <~ [sssSpell, sssI]

2017 @phenomnominal

PARSELTONGUE

JavaScript

TOKENS

LEXING

2017 @phenomnominal

PARSING

???

AST

Parsing

Parsing is the process of taking the lexical tokens and applying the grammar of a language to them.

2017 @phenomnominal

Variable Declaration

{ 
  type: 'Program',
  body: [{
    type: 'VariableDeclaration',
    declarations: [{ 
      type: 'VariableDeclarator',
      id: {
        type: 'Identifier',
        name: 'spell'
      },
      init: {
        type: 'Literal',
        value: 'Expecto patronum'
      }
    }]
  }]
}
sssSpell <~ 'Expecto patronum'

Identifier

Whitespace

Punctuator

Whitespace

String literal

2017 @phenomnominal

Function Declaration

sssMagic [sssSpell, sssIntensity]
    ...
{
  type: 'VariableDeclarator',
  id: {
    type: 'Identifier',
    name: 'magic
  },
  init: {
    type: 'FunctionExpression',
    params: [{
      type: 'Identifier',
      name: 'spell
  }, {
      type: 'Identifier',
      name: 'intensity 
  }],
  body: {
    type: 'BlockStatement',
    body: [{ /// }]
  }
}

Identifier

Whitespace

Punctuator

Identifier

Punctuator

Whitespace

Identifier

Punctuator

2017 @phenomnominal

'{"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name" :"spell"},"init":{"type":"Literal","value":"Expecto Patronum","raw":"\'Expecto Patronum\'"}}],"kind":"var"},{"type":"VariableDeclaration" ,"declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"magic"},"init":{"type":"FunctionExpression","id":null, "params":[{"type":"Identifier","name":"spell"},{"type":"Identifier","name":"intensity"}],"body":{"type":"BlockStatement","body":[{"type" :"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"intense"},"init":{"type":"Literal" ,"value":"","raw":"\'\'"}}],"kind":"var"},{"type":"ForStatement","init":{"type":"VariableDeclaration","declarations":[{"type": "VariableDeclarator","id":{"type":"Identifier","name":"i"},"init":{"type":"Literal","value":0,"raw":"0"}}],"kind":"var"},"test":{"type" :"BinaryExpression","operator":"<","left":{"type":"Identifier","name":"i"},"right":{"type":"Identifier","name":"intensity"}},"update":{"type":"UpdateExpression","operator":"++","argument":{"type":"Identifier","name":"i"},"prefix":false},"body":{"type":"BlockStatement" ,"body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name": "intense"},"right":{"type":"BinaryExpression","operator":"+","left":{"type":"Identifier","name":"intense"},"right":{"type":"Literal", "value":"!","raw":"\'!\'"}}}}]}},{"type":"ReturnStatement","argument":{"type":"BinaryExpression","operator":"+","left":{"type": "Identifier","name":"spell"},"right":{"type":"Identifier","name":"intense"}}}]},"generator":false,"expression":false}}],"kind":"var"},{"type":"ForStatement","init":{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier", "name":"i"},"init":{"type":"Literal","value":0,"raw":"0"}}],"kind":"var"},"test":{"type":"BinaryExpression","operator":"<","left":{"type":"Identifier","name":"i"},"right":{"type":"Literal","value":10,"raw":"10"}},"update":{"type":"UpdateExpression","operator":"++" ,"argument":{"type":"Identifier","name":"i"},"prefix":false},"body":{"type":"BlockStatement","body":[{"type":"ExpressionStatement", "expression":{"type":"CallExpression","callee":{"type":"Identifier","name":"magic"},"arguments":[{"type":"Identifier","name":"spell"},{"type":"Identifier","name":"i"}]}}]}}],"sourceType":"script"}'

2017 @phenomnominal

Now we just need to go FROM ast to JavaScript

Which sounds pretty hard...

2017 @phenomnominal

ESCODEGEN

import * as escodegen from 'escodegen';

let javascript = escodegen.generate(ast);

2017 @phenomnominal

2017 @phenomnominal

PARSELTONGUE

JavaScript

TOKENS

LEXING

PARSING

CODEGEN

AST

2017 @phenomnominal

It's almost time to try and see if we can save harry!

2017 @phenomnominal

But OH NO!

2017 @phenomnominal

Sometimes, when a witch or wizard gets stuck in the internet too long, they go a bit delirious from all the gifs and kittens, and they may try to use one of the...

2017 @phenomnominal

Unforgivable FUNCTIONS

imperio()
crucio()
avadakedavra()

2017 @phenomnominal

Let's see if we can come up with a way to stop that from happening!

2017 @phenomnominal

Inspecting the AST

There's a number of reasons you might want to do this:

  • Code transforming/formatting
  • Linting
  • Minifying
  • Mutating

2017 @phenomnominal

ESQuery

import esquery from 'esquery';

let nodes = esquery.query(ast, myQuery);

2017 @phenomnominal

Linting for the unforgivable functions:

CallExpression[callee.name="imperio"] Identifier
CallExpression[callee.name="crucio"] Identifier
CallExpression[callee.name="avadakedavra"] Identifier
let nodes = esquery.query(ast, UNFORGIVABLE_QUERY);
nodes.forEach(identifier => identifier.name = 'alert');

2017 @phenomnominal

2017 @phenomnominal

PARSELTONGUE

JavaScript

TOKENS

LEXING

PARSING

CODEGEN

AST

2017 @phenomnominal

INSPECTION

Resources:

https://github.com/dannysu/ecmascript1

https://github.com/estree/estree

https://github.com/jquery/esprima

https://github.com/estools/escodegen

http://estools.github.io/esquery/

https://medium.com/@kosamari/how-to-be-a-compiler-make-a-compiler-with-javascript-4a8a13d473b4#.jb7hdpm7z

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API

https://github.com/mozilla/source-map#generating-a-source-map

https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/

https://astexplorer.net

https://www.buzzfeed.com/zgalehouse/300-harry-potter-gifsthe-magic-never-ends-7sat?utm_term=.qk4pZBa22#.ktRkWQMPP

2017 @phenomnominal

Questions?

2017 @phenomnominal

Fantastic ASTs and Where To Find Them 2017

By Craig Spence

Fantastic ASTs and Where To Find Them 2017

  • 4,389