最最基础的编译原理

41 阅读3分钟

所有的编译器,都逃不出这5个阶段:

源代码
 ↓
词法分析(Lexical Analysis)
 ↓
语法分析(Syntax Analysis)
 ↓
语义分析(Semantic Analysis)
 ↓
中间表示(IR)
 ↓
代码生成 / 转换
第一个阶段:词法分析

把“字符流”变成“token流”。有点类似于给某些字符打上标签,分门别类。

一般会使用 正则匹配、状态机 两种方式进行词法分析。

正则匹配:但是正则匹配在编译器场景下,不太合适,因为想要大量的正则表示式并且有冲突,同时不易维护,性能不高。

状态机:有穷状态自动机(FSM),在有限个输入的情况下,在这些状态中转移并期望最终达到终止状态。

// 字符
c o n s t   a   =   1 ;
// token
[const] [identifier:a] [=] [number:1] [;]
第二个阶段:语法分析

语法分析的目的是通过词法分析拿到的token流,结合文法规则,通过一定算法得到一颗抽象语法树(AST)。这一个步骤中的基础是文法。

那么什么是文法呢?

编译器需要通过什么规则生成。这是一套规则。通常使用的是上下文无关文法(CFG)。

从生成AST效率和实现难度上,总结有两种解析算法:

  • 自顶而下,简单、递归下降
  • 自底而上就,强大、复杂

抽象语法树(AST)

树结构表示代码的逻辑结构。

[const] [a] [=] [1]

VariableDeclaration
 ├─ kind: const
 └─ Declarator
     ├─ Identifier(a)
     └─ Literal(1)
第三阶段:语义分析

让结构正确的代码变得有意义。主要对作用域、类型、符号表进行分析。语法正确并不表示语义正确。

let a = 1;
let a = 2; // 语法 OK,语义错误
第四阶段:中间表示(IR)

介于源语言和目标语言之间的统一表示,这一个过程是实现跨平台,统一优化逻辑的关键步骤。Babel的AST本质上就是IR

第五阶段:代码生成

把AST/IR输出成目标语言。

总流程
input => tokenizer => tokens; // 词法分析
tokens => parser => ast; // 语法分析、语义分析
ast => transformer => newAst; //中间表示
newAst => generator => output; //代码生成
公式编译执行器
// Tokenizer
function tokenizer(expression) {
  const tokens = [];
  let current = 0;
  while (current < expression.length) {
    let char = expression[current];
    if (/\d/.test(char)) {
      let number = "";
      while (/\d/.test(char)) {
        number += char;
        char = expression[++current];
      }
      tokens.push({ type: "number", value: number });
      continue;
    }
    if (/[a-zA-Z]/.test(char)) {
      let id = "";
      while (/[a-zA-Z]/.test(char)) {
        id += char;
        char = expression[++current];
      }
      while (expression[current] === " ") {
        current++;
      }
      if (expression[current] === "(") {
        tokens.push({ type: "function", value: id });
      } else {
        tokens.push({ type: "id", value: id });
      }
      continue;
    }

    if (char === "(" || char === ")" || char === ",") {
      tokens.push({ type: char, value: char });
      current++;
      continue;
    }

    if (char === ".") {
      tokens.push({ type: "dot", value: "." });
      current++;
      continue;
    }
    if (char === " ") {
      current++;
      continue;
    }
    throw new TypeError(`Unknown character: ${char}`);
  }

  return tokens;
}

// Parser
function parse(tokens) {
  let current = 0;
  function walk() {
    let token = tokens[current];
    if (token.type === "number") {
      current++;
      return { type: "NumberLiteral", value: token.value };
    }

    if (token.type === "function") {
      current++;
      const node = {
        type: "CallExpression",
        name: token.value,
        params: [],
      };
      token = tokens[++current];
      while (token.type !== ")") {
        node.params.push(walk());
        token = tokens[current];
        if (token.type === ",") {
          current++;
        }
      }
      current++;
      return node;
    }

    if (token.type === "id") {
      let value = token.value;
      current++;
      while (tokens[current] && tokens[current].type === "dot") {
        current++;
        if (tokens[current] && tokens[current].type === "id") {
          value += "." + tokens[current].value;
          current++;
        } else {
          throw new TypeError("Expect a variable after '.'");
        }
      }
      return { type: "Variable", value };
    }
    throw new TypeError(`Unknown token type: ${token.type}`);
  }
  let ast = { type: "Program", body: [] };
  while (current < tokens.length) {
    ast.body.push(walk());
  }
  return ast;
}

// Interpreter
function interpreter(ast, context = {}) {
  const operators = {
    Add: (a, b) => a + b,
    Subtract: (a, b) => a - b,
    Multiply: (a, b) => a * b,
    Divide: (a, b) => a / b,
  };
  function traverseNode(node) {
    if (node.type === "NumberLiteral") {
      return Number(node.value);
    }
    if (node.type === "Variable") {
      const keys = node.value.split(".");
      let value = context;
      for (let key of keys) {
        if (!(key in value)) {
          if (keys.indexOf(key) === keys.length - 1) {
            console.log(value, "...", key);
            throw new TypeError(`Unknown variable: ${node.value}`);
          } else {
            continue;
          }
        }
        value = value[key];
      }
      return value;
    }
    if (node.type === "CallExpression") {
      const args = node.params.map(traverseNode);
      const func = operators[node.name];
      if (!func) {
        throw new TypeError(`Unknown function: ${node.name}`);
      }
      return func.apply(null, args);
    }
    throw new TypeError(`Unknown node type: ${node.type}`);
  }
  return traverseNode(ast.body[0]);
}

const person = { age: 2 };
const expression =
  "Subtract(Add(3, Multiply(4, person.age)), Divide(6, person.age))";
const tokens = tokenizer(expression);
console.log(tokens);
const ast = parse(tokens);
const result = interpreter(ast, person);
console.log(result);