您的位置:首页 > 房产 > 建筑 > 股东分红如何合理节税_网站宣传的手段有哪些_跨境电商网站_互联网营销师证书是国家认可的吗

股东分红如何合理节税_网站宣传的手段有哪些_跨境电商网站_互联网营销师证书是国家认可的吗

2025/3/9 11:44:55 来源:https://blog.csdn.net/qq_34306228/article/details/145968964  浏览:    关键词:股东分红如何合理节税_网站宣传的手段有哪些_跨境电商网站_互联网营销师证书是国家认可的吗
股东分红如何合理节税_网站宣传的手段有哪些_跨境电商网站_互联网营销师证书是国家认可的吗

前端AST

  • 前端AST
    • 编译器
      • 什么是编译器
      • 编译器的思路
    • 手写编译器
      • 词法分析
      • 语法分析
      • 代码转换
      • 代码生成
      • 编译过程汇总

前端AST

  • 什么是AST、编译器
  • 编译器基本思路
  • 手写一个简单的编译器

编译器

什么是编译器

compiler - 编译器
将一种代码,转换为另外一种代码
输入代码,也可以叫 源代码
目标代码(浏览器识别代码):js / css

包括:

  • sass / less / stylus -> css
  • ts / vue / jsx / tsx / coffeescript(JS基础上,JS超集) -> js
  • eslint / tslint 语法检查首先要先将源代码变成可识别的代码

使用场景:

  • 对浏览器无法识别的代码,进行转换 -> es5
    很多支持 es6,但是不会转 es6,因为es5更安全,具备更大的兼容性
  • 热更新:举例,webpack热更新,微信小程序热更新,通过将最新的代码以字符串的形式给到端上,端将代码跑起来,js执行的前端页面,动态的修改 JS 代码,是不安全的,eval / new Function,被微信小程序禁止掉的
    想做热更新,那么自己可以写一个类似 eval,new Function,然后转换为JS可执行的代码,也就是在自己的代码中内置一个AST解析工具,可以把任何特定规定的字符串转成一段代码 => 让 JS 自己执行 JS
    自启动:用 GoLang 写一个 GoLang 的编译器
  • 代码压缩
  • 代码混淆(加密):前端编译器技术将代码做特性意义的混淆
  • 语法检查
  • 语法高亮
  • 代码自动补全(不是现在AI的这种,就是之前IDE中的简单补全)

编译器的思路

  1. 词法分析:将一段代码变成最小的一个个单元 -> tokens => 函数名:tokenizer
const test = require("tape")

最小单元:const、空格、test、=、require、(、)、“”

  1. 语法分析:-> AST => 函数名:parser
    将这段代码的含义分析出来,将一个个单元变成有含义的
    例如,const -> 关键字,test->变量名
  2. 代码转换 -> 源AST => 目标AST =>函数名:transformer+traverse

AST主要是给开发人员看的,形式化的语言,将这段代码变成与本身代码没有具体关系的

  1. 代码生成 -> ES5 =>函数名:generator

上述思路以及后续的命名都是参考的 Babel 的,Babel 是常规编译器的具体表现
C语言 和 Babel 类似,C语言连接编译,只是 C语言 最终转的为 机器码

es6 -> @babel/parser(做AST生成的) -> AST -> @babel/traverse(一种AST转为另外一种AST) -> new AST -> es5

Taro:将 Taro 语言的代码结构转换为小程序的代码结构,其实用的就是Babel,写了一个自己的 babel/parser+babel/traverse,将它的语法结构转化为 AST

手写编译器

根据上述所讲的四个步骤,将 input 代码经过 tokenizer,parser,transformer,codeGenerator 转换成各个阶段对应的所需结果,最终生成 output。

test.js:

const {tokenizer,parser,transformer,codeGenerator,compiler,
} = require('./utils');// 做各种断言的,判断值,函数调用后期望的值是否符合预期const assert = require('assert');const input  = '(add 2 (subtract 4 2))'; // lisp//4. 代码生成所生成的JS代码const output = 'add(2, subtract(4, 2));'; // JS// 1. 词法分析生成的结果const tokens = [{ 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: ')'        }];// lisp  2.语法分析所生成的AST结构const 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'}]}]}]};// 3.代码转换生成的新的JS的AST结构const newAst = {type: 'Program',body: [{type: 'ExpressionStatement',//expression:表达式expression: {type: 'CallExpression',callee: {type: 'Identifier',name: 'add'},//表达式的参数列表arguments: [{type: 'NumberLiteral',value: '2'}, {type: 'CallExpression',//callee就是调用方,当前函数本身的信息callee: {type: 'Identifier',name: 'subtract'},arguments: [{type: 'NumberLiteral',value: '4'}, {type: 'NumberLiteral',value: '2'}]}]}}]};//deepStrictEqual  深度比较两个值是否相等assert.deepStrictEqual(tokenizer(input), tokens, 'Tokenizer should turn `input` string into `tokens` array');assert.deepStrictEqual(parser(tokens), ast, 'Parser should turn `tokens` array into `ast`');assert.deepStrictEqual(transformer(ast), newAst, 'Transformer should turn `ast` into a `newAst`');assert.deepStrictEqual(codeGenerator(newAst), output, 'Code Generator should turn `newAst` into `output` string');assert.deepStrictEqual(compiler(input), output, 'Compiler should turn `input` into `output`');console.log('All Passed!');
const input  = '(add 2 (subtract 4 2))';

可以看成是 function xxx(function add(2,function subtract(4,2))) 这样的函数

const output = 'add(2, subtract(4, 2));'; // JS

=> function add(2,function subtract(4,2)) 这样

词法分析

第一步:词法分析:将这个input转换为tokens
在这里插入图片描述

  • 状态机:一类状态转换为另外一类状态的结果
    input 是一种状态,tokens是另一种状态
  • 有穷状态机:转换的结果是确定性的 - 简单理解:循环
    • 非确定有穷状态机:一个输入可能有多个输出
    • 确定有穷状态机:一个输入代表一个确定的输出
      而上述将 input 状态 转换为 tokens的状态,这种就叫确定有穷状态机

tokens的类型只有三种:paren、name、number

const tokenizer = (input) => {const tokens = [];// 判断是否是字符串if (typeof input !== 'string') {return tokens;}for (let i = 0; i <= input.length - 1; i++) {const char = input[i];switch (true) {case char === ' ':  //空格不处理break;case char === '(' || char === ')':  //括号处理tokens.push({type: 'paren',value: char,});break;case isStrNumber(char):   //数字处理let fullNum = char;//取下一位,因为一位数,两位数,三位数等都有可能出现let nextChar = input[++i];//下一位不是数字if (!isStrNumber(nextChar)) {i--;}//下一位是数字,循环遍历到不是数字while(isStrNumber(nextChar)) {fullNum += nextChar;nextChar = input[++i];}tokens.push({type: 'number',value: fullNum,});break;default: //字符串处理let fullStr = char;let nextStr = input[++i]//判断下一个是否是字符串或者数字,函数名可能为:add1这种类型if (!(isStrLetter(nextStr) || isStrNumber(nextStr))) {i--;}while (isStrLetter(nextStr) || isStrNumber(nextStr)) {fullStr += nextStr;nextStr = input[++i];}tokens.push({type: 'name',value: fullStr,});break;}}return tokens;
}
//判断是否是数字
const isStrNumber = (str) => {if (typeof str === 'number') {return true;}// 不是字符串也不是数字if (typeof str !== 'string') {return false;}if (str === ' ' || str === '') {return false;}return !isNaN(str);
}
//判断是否是字符串
const isStrLetter = (str) => {if (typeof  str !== 'string') {return false;}//空格或空字符串if (str === ' ' || str === '') {return false;}// 通过 正则,asci码,toUpperCase和toLowerCase,判断字符串是否是26个字母的其中一个return str.toLowerCase() !== str.toUpperCase();
}

通过 正则,asci码,toUpperCase和toLowerCase,判断字符串是否是26个字母的其中一个

语法分析

讲究代码本身的含义
第二步,语法分析所生成的AST结构
在这里插入图片描述

看到嵌套结构,想到可能要写一个递归,由于嵌套结构是不可预测的

const parser = (tokens) => {const ast = {type: 'Program',body: []};let current = 0;const handler = () => {let item = tokens[current];current++;if (!item || !item.type) {return;}switch(true) {//先判断简单的-先判断numbercase item.type === 'number':return {type: 'NumberLiteral',value: item.value,};//paren和name是一起出现的,而且paren的出现代表着一个函数的开始,所以这里判断paren类型case item.type === 'paren' && item.value === '(':  //一个逻辑结构的开始//current 已经指向下一个位置了,所以这个取得是下一个,也就是函数的名字item = tokens[current];const astNode = {type: 'CallExpression',name: item.value,params: [],};//在往后移一个,下下一个是函数的参数item = tokens[++current];//当前元素不是括号类型,或者是括号类型但是不是 ) 的类型 while (item.type !== 'paren'|| (item.type === 'paren' && item.value !== ')')// !(item.value === ')')) {//括号里就是一个完整的新结构了,可以进行递归调用了const subItem = handler();if (subItem) {astNode.params.push(subItem);}item = tokens[current];}current++;return astNode;}};while(current < tokens.length) {const result = handler();if (result) {ast.body.push(result);}}return ast;
}

代码转换

第三步,代码转换生成的新的JS的AST结构
在这里插入图片描述

使用 babel 的思路,transform + traverse,为了方便做插件开发的。
将代码从上往下做解析了,解析的过程不在函数里面去写,而是在外部定义一个访问者叫visitor(其实就是一个map),告诉traverse应该怎么去转换代码,具体怎么遍历,怎么做上下文的管理,在visitor中不用去管,visitor只局限于当前一个的处理,具体的上下文,从前往后的遍历,状态管理等,都是traverse来做的。
也就是说,traverse本身变成了负责从前到后,从里到外,从外到里进行AST递归的产物,再告诉traverse怎么去处理每一种状态。

// 只做一件事,一个一个的转换结构
const traverser = (ast, visitor) => {// 两种情况:处理单个成员,处理数组// 生成body[0]成员const traverserNode = (node, parent) => { //需要知道成员本身,以及成员的上下文(当个数组来说,上下文就是父节点)// 拿到visitor中的enter函数const enter = visitor[node.type]?.enter; if (enter) {// 调用enter函数enter(node, parent);}switch (node.type) {// number不需要处理,因为上面已经调用过了case 'CallExpression':traverserArray(node.params, node); //当前节点:node的子节点  父节点:node本身break;case 'Program':traverserArray(node.body, node);break;}};// 生成params数组成员const traverserArray = (nodes, parent) => {nodes.forEach(node => {traverserNode(node, parent);});};traverserNode(ast, null);};// transform只做两件事情,1.调用traverser,2.声明traverser应该用什么样的方法去转换两种不同的结构
const transformer = (ast) => {const newAst = {type: 'Program',body: []};ast._context = newAst.body;traverser(ast, {//visitor像写插件的窗口处理函数// 函数处理CallExpression: {enter(node, parent) {// 给到父级对象成员,让父级对象成员统一去处理,因为父级对象成员可能有n个对象成员,父级成员中定义一个变量,管理父级成员中所有的子成员,就可以从内部往上一级一级遍历,也就是先将叶子节点转换完,再一层一层往上给,最后将值存到parent中就行let expression = {type: 'CallExpression',callee: {type: 'Identifier',name: node.name,},// 不让子组件直接操作arguments,因为arguments本身还要做特殊的处理arguments: [],};// 因此给node添加一个属性,让子组件去操作上下文,最终将_context中的值赋给argumentsnode._context = expression.arguments;if (parent?.type !== 'CallExpression') {// 当前节点是根组件expression = {type: 'ExpressionStatement',expression: expression};}// 如果父组件也是函数的话,就将当前表达式的信息给到父组件parent?._context?.push(expression);}},// 数字结构NumberLiteral: {enter(node, parent) {// 由此这里操作的是父组件的_contextparent?._context?.push({type: 'NumberLiteral',value: node.value,});}},});return newAst;
}

代码生成

第四步,代码生成所生成的JS代码
将input代码转换为output代码
在这里插入图片描述

const codeGenerator = (newAst) => {// 5种情况,单层结构中只关注这五种情况,五种结构中自己再去调用codeGenerator即可switch (newAst.type) {// 数字和标识符最简单,先处理这两个case 'Identifier':return newAst.name;case 'NumberLiteral':return newAst.value;// 处理函数 subtract(4, 2)case 'CallExpression':return [codeGenerator(newAst.callee),'(',newAst.arguments.map(codeGenerator).join(', '),')'].join('')case 'ExpressionStatement':return codeGenerator(newAst.expression) + ';';case 'Program':return newAst.body.map(codeGenerator).join('\n');}
}

编译过程汇总

使用compiler函数将上述所有函数包裹一起:

const compiler = (input) => {return codeGenerator(transformer(parser(tokenizer(input))));
};

这样输入input 会得到对应的 output 结果

所有内容汇总:
utils.js:

const isStrNumber = (str) => {if (typeof str === 'number') {return true;}if (typeof str !== 'string') {return false;}if (str === ' ' || str === '') {return false;}return !isNaN(str);
}const isStrLetter = (str) => {if (typeof  str !== 'string') {return false;}if (str === ' ' || str === '') {return false;}return str.toLowerCase() !== str.toUpperCase();
}const tokenizer = (input) => {const tokens = [];if (typeof input !== 'string') {return tokeens;}for (let i = 0; i <= input.length - 1; i++) {const char = input[i];switch (true) {case char === ' ':break;case char === '(' || char === ')':tokens.push({type: 'paren',value: char,});break;case isStrNumber(char):let fullNum = char;let nextChar = input[++i];if (!isStrNumber(nextChar)) {i--;}while(isStrNumber(nextChar)) {fullNum += nextChar;nextChar = input[++i];}tokens.push({type: 'number',value: fullNum,});break;default:let fullStr = char;let nextStr = input[++i]if (!(isStrLetter(nextStr) || isStrNumber(nextStr))) {i--;}while (isStrLetter(nextStr) || isStrNumber(nextStr)) {fullStr += nextStr;nextStr = input[++i];}tokens.push({type: 'name',value: fullStr,});break;}}return tokens;
}const parser = (tokens) => {const ast = {type: 'Program',body: []};let current = 0;const handler = () => {let item = tokens[current];current++;if (!item || !item.type) {return;}switch(true) {case item.type === 'number':return {type: 'NumberLiteral',value: item.value,};case item.type === 'paren' && item.value === '(':item = tokens[current];const astNode = {type: 'CallExpression',name: item.value,params: [],};item = tokens[++current];while (item.type !== 'paren'|| (item.type === 'paren' && item.value !== ')')// !(item.value === ')')) {const subItem = handler();if (subItem) {astNode.params.push(subItem);}item = tokens[current];}current++;return astNode;}};while(current < tokens.length) {const result = handler();if (result) {ast.body.push(result);}}return ast;
}// 只做一件事,一个一个的转换结构
const traverser = (ast, visitor) => {// 两种情况:处理单个成员,处理数组// 生成body[0]成员const traverserNode = (node, parent) => { //需要知道成员本身,以及成员的上下文(当个数组来说,上下文就是父节点)// 拿到visitor中的enter函数const enter = visitor[node.type]?.enter; if (enter) {// 调用enter函数enter(node, parent);}switch (node.type) {// number不需要处理,因为上面已经调用过了case 'CallExpression':traverserArray(node.params, node); //当前节点:node的子节点  父节点:node本身break;case 'Program':traverserArray(node.body, node);break;}};// 生成params数组成员const traverserArray = (nodes, parent) => {nodes.forEach(node => {traverserNode(node, parent);});};traverserNode(ast, null);};// transform只做两件事情,1.调用traverser,2.声明traverser应该用什么样的方法去转换两种不同的结构
const transformer = (ast) => {const newAst = {type: 'Program',body: []};ast._context = newAst.body;traverser(ast, {//visitor像写插件的窗口处理函数// 函数处理CallExpression: {enter(node, parent) {// 给到父级对象成员,让父级对象成员统一去处理,因为父级对象成员可能有n个对象成员,父级成员中定义一个变量,管理父级成员中所有的子成员,就可以从内部往上一级一级遍历,也就是先将叶子节点转换完,再一层一层往上给,最后将值存到parent中就行let expression = {type: 'CallExpression',callee: {type: 'Identifier',name: node.name,},// 不让子组件直接操作arguments,因为arguments本身还要做特殊的处理arguments: [],};// 因此给node添加一个属性,让子组件去操作上下文,最终将_context中的值赋给argumentsnode._context = expression.arguments;if (parent?.type !== 'CallExpression') {// 当前节点是根组件expression = {type: 'ExpressionStatement',expression: expression};}// 如果父组件也是函数的话,就将当前表达式的信息给到父组件parent?._context?.push(expression);}},// 数字结构NumberLiteral: {enter(node, parent) {// 由此这里操作的是父组件的_contextparent?._context?.push({type: 'NumberLiteral',value: node.value,});}},});return newAst;
}
const codeGenerator = (newAst) => {// 5种情况,单层结构中只关注这五种情况,五种结构中自己再去调用codeGenerator即可switch (newAst.type) {// 数字和标识符最简单,先处理这两个case 'Identifier':return newAst.name;case 'NumberLiteral':return newAst.value;// 处理函数 subtract(4, 2)case 'CallExpression':return [codeGenerator(newAst.callee),'(',newAst.arguments.map(codeGenerator).join(', '),')'].join('')case 'ExpressionStatement':return codeGenerator(newAst.expression) + ';';case 'Program':return newAst.body.map(codeGenerator).join('\n');}
}const compiler = (input) => {  //层层嵌套return codeGenerator(transformer(parser(tokenizer(input))));
};module.exports = {tokenizer,parser,transformer,codeGenerator,compiler
}

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com