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:
- A VariableDeclaration is used to assign the Identifier’s A Identifier to a NumericLiteral value of 3
- 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/