
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 <~ truesssMultiply [sssA, sssB, sssC]
    <~ sssA * sssB * sssCFunctions:
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
    sssDoDefaultif (something) {
    doSomething();
} else if (somethingElse) {
    doSomethingElse();
} else {
    doDefault();
}Parseltongue
JavaScript
Loops:
sssStart <- 5
sssEnd <~ 10
sssss sssI <~ sssStart ~> sssEnd
    sssDoSomething sssIfor (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"] Identifierlet 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
- 5,975
 
   
   
  