Original: https://www.sitepoint.com/understanding-asts-building-babel-plugin/

Only important parts are selected for translation in this paper

Language is introduced

We designed a plug-in to convert normal Objects and arrays into persistent data structures called Mori

The code we want to write looks like this:

var foo = { a: 1 };
var baz = foo.a = 2;
foo.a === 1;
baz.a === 2;
Copy the code

What you want to convert is:

var foo = mori.hashMap('a', 1);
var baz = mori.assoc(foo, 'a', 2);
mori.get(foo, 'a') = = = 1; mori.get(baz,'a') = = = 2;Copy the code

Babel

Babel’s main processing process consists of three parts:

Parse

Babylon parses and understands Javascript code

Transform

Babel-traverse analysis and modification of AST

Generate

Babel-generator converts the AST tree back to normal code

AST Abstract syntax tree

Understanding the AST is the foundation for what follows. The Javascript language is generated from a string of strings, each with some visual semantic information. This is useful to all of us because it allows us to use matching characters ([], {}, ()), paired characters (“”, ”), and indentation to better understand the program. And then it makes no sense to the computer. To them, each character is just a number in memory, and they can’t use them to ask high-level questions like “How many variables are in this declaration? Instead, we need to compromise and find a way to make code programmable and something computers can understand.

The code looks like this

var a =3;
a + 5
Copy the code

The AST tree obtained by parsing

All AST starts at the root of a Program node, which contains the top-level expression of the Program. In this example, we only have two:

  1. A VariableDeclaration is used to assign the Identifier’s A Identifier to a NumericLiteral value of 3
  2. An ExpressionStatement consists of a BinaryExpression, consisting of the Identifier’s “A” Identifier and operator “+”, as well as the number 5

Although they are made up of simple blocks, the size of the AST means that they are quite complex, especially for important projects. Instead of trying to understand the AST ourselves, we can use AstExplorer.net, which allows us to type in Javascript code on the left and output the AST on the right. We will use this tool to understand and experiment with code.

For Babel stability, choose to use “Babylon6” as an interpreter.

Setup

Make sure you install using Node and NPM. Create a project file, create a package.json file and install the following dependencies

mkdir moriscript && cd moriscript
npm init -y
npm install --save-dev babel-core
Copy the code

We create a file plug-in and export a default function

// moriscript.js
module.exports = function(babel) {
  var t = babel.types;
  return {
    visitor: {

    }
  };
};

Copy the code

Babel provides a visitor pattern that can be used to write various plug-ins, insert, delete, and so on to generate a new AST tree

// run.js
var fs = require('fs');
var babel = require('babel-core');
var moriscript = require('./moriscript');

// read the filename from the command line arguments
var fileName = process.argv[2];

// read the code from this file
fs.readFile(fileName, function(err, data) {
  if(err) throw err;

  // convert from a buffer to a string
  var src = data.toString();

  // use our plugin to transform the source
  var out = babel.transform(src, {
    plugins: [moriscript]
  });

  // print the generated code to screen
  console.log(out.code);
});

Copy the code

An array of Arrays

MoriScript’s first task is to convert Object and Array into their Mori counterparts: HashMapsh and Vector. The first thing we’re going to convert is Array.

var bar = [1.2.3];
// should becom
var bar = mori.vector(1.2.3);
Copy the code

Copy the above code into astexplorer and highlight the array [1,2,3] to see the corresponding AST node.

For readability we select only AST nodes in the data region:

// [1, 2, 3]
{
    "type": "ArrayExpression",
    "elements":[
        {
            "type": "NumericLiteral",
            "value": 1
        },
        {
            "type": "NumericLiteral",
            "value": 2
        },{
            "type": "NumericLiteral",
            "value": 3
        }
    ]
}
Copy the code

The AST of mori. Vector (1,2,3) is as follows:

{
    "type": "CallExpression"."callee": {
        "type": "MemberExpression"."object": {
            "type": "Identifier"."name": "mori"
        },
        "property": {"type": "Identifier"."name": "vector"}},"arguments":[
        {
            "type": "NumericLiteral"."value": 1
        },
        {
            "type": "NumericLiteral"."value": 2
        },
        {
            "type": "NumericLiteral"."value": 3}}]Copy the code

The above visualization of the nodes clearly shows the difference between the two trees

Now we can clearly see that we need to replace the top-level expression, but we can share the numeric expression between the two trees.

Let’s start adding our first ArrayExpression to our visitor:

module.exports = function(babel) {
  var t = babel.types;
  return {
    visitor: {
      ArrayExpression: function(path) {

      }
    }
  };
};
Copy the code

We can find the corresponding expression type from the babel-types document. In this example we want to replace ArrayExpression with a CallExpression. We can generate T. calxpression (callee, arguments). The next thing you need is to call MemberExpression with t.memberExpression(Object, property).

ArrayExpression: function(path){
    path.replaceWith(
        t.callExpression(
            t.memberExpression(t.identifier('mori'), t.identifier('vector')), 
            path.node.elements
        )
    )    
        
}
Copy the code

Object

Now let’s look at Object

var foo = { bar: 1};
var foo =mori.hashMap('bar', 1);Copy the code

The object syntax has a similar structure to ArrayExpression

Highlight mori. HashMap (‘bar’, 1) to get:

{
  "type": "ObjectExpression"."properties": [{"type": "ObjectProperty"."key": {
        "type": "Identifier"."name": "bar"
      },
      "value": {
        "type": "NumericLiteral"."value": 1}}]}Copy the code

Visualized AST tree:

ObjectExpression: function(path){
    var props = [];
    path.node.properties.forEach(function(prop){
        props.push(
            t.stringLiteral(prop.key.name),
            prop.value
        );
    });
    path.replaceWith(
        t.callExpression(
            t.memberExpression(t.identifier('mori'), t.identifier('hasMap')),
            props
        )
    )
}
Copy the code

Similarly we have a CallExpression that surrounds a MemberExpression. It’s very similar to the Array code, except we need to do something a little more complicated to get the properties and values.

Assignment

foo.bar = 3;
mori.assoc(foo, 'bar'.3);
Copy the code
AssignmentExpression: function(path){
    var lhs = path.node.left;
    var rhs = path.node.right;
    
    if(t.isMemberExpression(lhs)){
        if(t.isIdentifier(lhs.property)){ lhs.property = t.stringLiteral(lhs.property.name); } path.replaceWith( t.callExpression( t.memberExpression(), [lhs.object, lhs.property, rhs] ) ); }}Copy the code

Membership

foo.bar;
mori.get(foo, 'bar');
Copy the code
MemberExpression: function(path){
    if(t.isAssignmentExpression(path.parent)) return;
    if(t.isIdentifier(path.node.property)){
        path.node.property = t.stringLiteral(path.node.property.name)
    }
    path.replaceWith(
        t.callExpression(
            t.memberExpression(),
            [path.node.object, path.node.property]
        )
    )
}
Copy the code

One problem is that the mori. Get obtained is a MemberExpression, leading to circular recursion

// set a flag to  recognize express has been tranverse
MemberExpression: function(path){
    if(path.node.isClean) return; . }Copy the code

Babel can only convert Javascript which Babel Parser understands

AST Online Parse: https://astexplorer.net/