The flowering period of The Chinese rose was so long that it was still open even after the end of May.

The first thing to understand is the concept

Lisp-like function and C-like function

If these two concepts are unfamiliar, here’s a simple example. Suppose we had two methods Add and subtract, they might be written like this.

/** * LISP C * * 2 + 2 (add 2 2) add(2, 2) * 4 - 2 (subtract 4 2) subtract(4, 2) * 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2)) /Copy the code

It seems simple enough, and while this is not a complete LISP or C syntax, it is enough to illustrate many of the major parts of modern compilers.

The compiler parses the process

Most compilers are divided into three main stages: parsing, transformation, and code generation.

1. Parsing is the transformation of original code into a more abstract representation of code.

2. Transformations take these abstract representations and operations to achieve the results the compiler expects.

3. Code generation Parses transformed content into new code.

Parsing stage

The analytical stage can be divided into two parts: lexical analysis and syntactic analysis.

  1. Lexical analysis

Lexical Analysis breaks the original code into Tokens and hands them to the Tokenizer calls. Tokens are small arrays of Tokens that describe individual syntactical parts. They can be numbers, labels, punctuation marks, operators, and more.

  1. Syntactic analysis

Syntactic Analysis reformats Tokens into something that represents the relationships of grammar. It’s called the Abstract syntax tree.

Abstract syntax tree (AST) is a deeply nested object. It can be used in a way that is both easy to use and tells us a lot about the code.

Example: For this syntax (add 2 (subtract 4 2)). Tokens may be the following

/* [ { type: 'paren', value: '(' }, { type: 'name', value: 'add' }, { type: 'number', value: '2' }, { type: 'paren', value: '(' }, { type: 'name', value: 'subtract' }, { type: 'number', value: '4' }, { type: 'number', value: '2' }, { type: 'paren', value: ')' }, { type: 'paren', value: ')' }, ] */
Copy the code

And the abstract syntax tree might look something like this:

/* * { * type: 'Program', * body: [{ * type: 'CallExpression', * name: 'add', * params: [{ * type: 'NumberLiteral', * value: '2', * }, { * type: 'CallExpression', * name: 'subtract', * params: [{ * type: 'NumberLiteral', * value: '4', * }, { * type: 'NumberLiteral', * value: '2', * }] * }] * }] * } */
Copy the code

Phase transformation

The next phase of the compiler is the transformation phase. This stage makes some changes to the abstract syntax tree obtained in the previous step. This operation can be done in the same programming language or in other programming languages.

You may have noticed that this abstract syntax tree is similar to the deeply nested objects we usually write, in that each object is a node of an abstract syntax tree.

For example, a number literal node:

  {
    type: 'NumberLiteral'.value: '2',}Copy the code

Or the node of an expression:

   {
     type: 'CallExpression'.name: 'subtract'.params: [...nested nodes go here...],
   }
Copy the code

When we transform the abstract syntax tree, we can dynamically add, delete, or replace these nodes, or we can clone a copy of the copied object to make changes.

Traversal During the transformation, we need a depth-first way to traverse each node of the abstract syntax tree. Suppose we need to traverse the following AST:

{
      type: 'Program'.body: [{
        type: 'CallExpression'.name: 'add'.params: [{
          type: 'NumberLiteral'.value: '2'
        }, {
          type: 'CallExpression'.name: 'subtract'.params: [{
            type: 'NumberLiteral'.value: '4'
          }, {
            type: 'NumberLiteral'.value: '2'}}}}]]]Copy the code

The process might be as follows:

  1. Program starts at the top of the Program.

  2. CallExpression moves to the first element of the program body

  3. NumberLiteral (2) moves to the first element of the program argument

  4. The CallExpression (subtract) moves to the second element of the program parameter

  5. NumberLiteral (4) moves to the first parameter of CallExpression (subtract)

  6. NumberLiteral(2) moves to the second parameter of CallExpression (subtract)

Code generation

The last step in the compiler is to generate code. Most of the time code generation just returns code from the abstract syntax tree as a string.

Code generators work in several different ways, either by re-using Tokens or by recreating a code block.

Of course, there is a recursive process involved. (/ ^ del ^) /

conclusion

Here’s an overview of the compilation process. The overall process is as follows:

Lexical Analysis –> Syntax –> Abstract Syntax tree –> Transformation Stage Further analysis of the abstract syntax tree –> Finally code generation stage –> Return strings of code.

The process is similar to that of Vue, where the Dom is first disassembled into the VNode virtual Dom, then the VNode is reparsed, compiled, and finally rendered back into the Dom.

At this point, the concept of DSL comes to mind. The full name Domain System Lauguage translates to system-specific language. The front-end seems to be used more for low-code platform design, and it seems to be more like a custom code format from personal understanding.

For example, the former front-end template art-Template, or Vue itself is a template-dependent framework, and its {{XXXX}} syntax seems to be understood as a DSL.

So, it’s worth thinking about how to customize a DSL.

Click to add a follow

One last word

  1. Move your rich little hands and “like it.”
  2. Move your rich little hands, “click here”
  3. All see here, might as well “add a follow”
  4. Might as well “forward”, good things to remember to share