编译原理-基本流程

375 阅读5分钟
基本流程

image.png

esprima

esprima是一个用于对js代码做词法分析或者语法分析的工具,后面就会用这个工具进行示范
体验地址
注意:他只支持js,不支持flow或者typescript

编译器

编译器就是将一种语言转换成另一种语言,可以是从高级语言转为低级语言也可以是高级语言转为高级语言
由上图可以看出整个编译过程是由多个阶段组成的,每个阶段处理的问题都不同,当前阶段仅仅依赖于上一个阶段输出的结果,不会跨阶段,比如在语法分析阶段就是根据词法分析产生的标记tokens进行分析,它不可能会接触到源码

词法分析 Lexical analysis

image.png
这是第一阶段,接收原始代码,然后把它分割成一些被词法单位称为token的东西,这个是在词法分析器中完成的
token是一个数组,由一个个被分割成的字符串,数字,标签,运算符或者其他任何被分割的东西组成

const esprima = require('esprima')
const program = 'hello world!'
const tokens = esprima.tokenize(program)
console.log(tokens)
最后生成的tokens数组
[
  { type: 'Identifier', value: 'hello' },
  { type: 'Identifier', value: 'world' },
  { type: 'Punctuator', value: '!' }
]


const program1 = '<h1><span>hello</span>world</h1>'
console.log(esprima.tokenize(program1))
[
  { type: 'Punctuator', value: '<' },
  { type: 'Identifier', value: 'h1' },
  { type: 'Punctuator', value: '>' },
  { type: 'Punctuator', value: '<' },
  { type: 'Identifier', value: 'span' },
  { type: 'Punctuator', value: '>' },
  { type: 'Identifier', value: 'hello' },
  { type: 'Punctuator', value: '<' },
  {
    type: 'RegularExpression',
    value: '/span>world</h1',
    regex: { pattern: 'span>world<', flags: 'h1' }
  },
  { type: 'Punctuator', value: '>' }
]

可以看出词法分析是把源代码从左到右依次进行识别出最小的词法单元即token,然后将它们放在一个数组中。
在解析过程中会用到正则来做分词,当然这里就不仅仅是正则了会用到正则的原理DFA,可以参考# Regex 正则表达式原理及如何从零实现(houbb.github.io/2020/01/07/…

语法分析Syntactic analysis

image.png
在这个阶段是接收之前的词法分析生成的token,然后把他们转换成一种抽象语法树Abstract Syntax Tree缩写为AST。
这个阶段会涉及到很多的算法当然这些算法不在谈论的范围内,比如上下无关文法,自顶向下,递归下降,LL(1),自底向上,LR(0)

const esprima = require('esprima')

var program = 'const str = hello';
console.log(esprima.parseScript(program))

最后输出的树结构
Script {
  type: 'Program',
  body: [
    VariableDeclaration {
      type: 'VariableDeclaration',
      declarations: [Array],
      kind: 'const'
    }
  ],
  sourceType: 'script'
}

AST Explorer astexplorer.net/ 这个上面可以在线解析出ast,支持各种语言同样的代码我们放到上面解析出来的ast如下 image.png
词法分析和语法分析在编译器的工作流程中为解析parse,这个是重点但不是难点,不论是token还是ast都有工具可以自动生成,并不需要自己手写,编程是从AST开始的,只有到了这个阶段我们的计算机才可以对编程语言进行分析,解释或者翻译的过程而这个过程是整个编译原理中最重要的一个部分
程序一旦被转换成抽象语法树则源代码即被丢弃后续的阶段只处理抽象语法树

语义分析

image.png 语义分析也称为类型检查,上下文相关分析,产生介于源代码和目标代码之间的一种代码,有的时候也会直接生成目标代码
主要任务是收集标识符的语义信息和语义检查比如:
1、变量在使用前先进行声明
2、变量有合适的类型
3、函数的调用和定义一致
...
在经过这一步骤之后的代码是不会有错误了,就算运行出错也不是代码的错误了而是编译器出错了
在这个过程中会生成符号表,用来存储收集的标识符,相当于一个缓存,在后面的阶段中遇到该标识符会在这个表中查找而不会去遍历AST

处理AST

在经过上面的一系列流程过后,计算机就能理解我们写出的代码了也就是AST
1、转译器transpiler:高级语言转为高级语言也就是把AST转成另外一个AST然后生成目标代码比如ts转成js前端很多这种转译器比如babel,postcss,eslint等转译器也是编译器的一种,只不过比较特殊
2、编译器compiler:高级语言转为低级语言比如c语言编译后生成的可执行文件。这个过程就比较复杂了是把AST经过递归对每个节点进行翻译一般是语法制导翻译,语法制导翻译的基本思想是给每条产生式规则附加一个语义动作,这个语义动作在产生式归约时执行。然后翻译成线性代码然后生成汇编代码或者机器码
对于前端来说我们接触的都是转译器,编译器处理AST那些流程根本接触不到
3、解释器:运行时编译生成机器代码比如v8

总结: 根据不同处理AST的方式有转译器,编译器和解释器。
转译器是特殊的编译器。
编译器最后都会生成目标代码,解释器不会生成代码

参考

1、编译原理一:想初步了解编译原理?看这篇文章就够了
2、写给前端的编译原理科普