编译原理三:词法分析

概念

  • 在编译器工作流程中,词法分析是将源代码分解为一系列词法单元的过程。

  • 词法单元包括标识符关键字运算符等。词法分析器会读取源代码的每一个字符根据预定义的规则将它们组成一系列词法单元

  • 词法分析器通常使用有限状态机来实现。有限状态机是一种计算模型,它可以接受一串输入并根据一组状态转移规则进行状态转移,最终输出一个结果。在词法分析器中,有限状态机通常用于匹配输入的字符串。

  • 词法分析器可以快速匹配输入的字符串,并且不需要回溯的操作,因此能够有效提高编译器的词法分析效率

  • 词法分析器通常是编译器的第一个组件,它会将源代码分解为一系列词法单元,然后将这些词法单元传递给语法分析器进行语法分析。词法分析器的设计和实现对编译器的性能和正确性都有重要的影响。

例子:实现一个基本的词法分析器

需求:将<h1 id="title"><span>hello</span>world</h1>转换成一系列词法单元。
最终效果:

在这里插入图片描述

  • 定义类型
// tokenTypes.js
const Punctuator = "Punctuator"; // < > / 等一系列标识符
const JSXIdentifier = "JSXIdentifier"; // html标签
const AttributeKey = "AttributeKey"; // 标签属性的key(如:id, name等)
const AttributeStringValue = "AttributeStringValue"; // 标签属性的value(如:id="title"中的title)
const JSXText = "JSXText"; // 文本(如标签内部的文字)
const BackSlash = "BackSlash"; // 反斜杠(结束标签的/)

// 将类型导出
module.exports = {
  Punctuator,
  JSXIdentifier,
  AttributeKey,
  AttributeStringValue,
  JSXText,
  BackSlash,
};
  • 具体实现
// tokenizer.js

// 导入类型
const {
  Punctuator,
  JSXIdentifier,
  AttributeKey,
  AttributeStringValue,
  JSXText,
  BackSlash,
} = require("./tokenTypes");

// 初始的token
let currentToken = { type: "", value: "" }; 
// 分词数组,最终生成的分词数据都会放在这里
let tokens = []; 
// 定义正则,符合大写字母、小写字母、数字,则做什么事情
const LETTER = /[a-zA-Z0-9]/;

// 主函数
function tokenlizier(input) {
  // 开始状态
  let state = start;
  debugger;
  // 遍历收到的字符
  for (let char of input) {
    state = state(char);
  }
  return tokens;
}

// 首次执行时,匹配到 <
function start(char) {
  if (char === "<") {
    // 将类型及值发射出去,存到 tokens 中
    emit({ type: Punctuator, value: "<" });
    // 找到 < 之后继续收集
    return foundLeftParentheses;
  }
  // 未找到 < 则抛出异常
  throw new Error("第一个字符必须是<");
}

function foundLeftParentheses(char) {
  // char: h1
  if (LETTER.test(char)) {
    currentToken.type = JSXIdentifier;
    currentToken.value += char;
    // 继续收集标识符
    return jsxIdentifier;
  } else if (char === "/") {
    // 区分开始标签和结束标签
    emit({ type: BackSlash, value: "/" });
    return foundLeftParentheses;
  }
  throw TypeError("Error");
}

function jsxIdentifier(char) {
  // char如果是字母或数字就接着增加
  if (LETTER.test(char)) {
    currentToken.value += char;
    // 继续收集标识符
    return jsxIdentifier;
  } else if (char === " ") {
    // 遇到了空格, 标识符结束,将当前收集的token发射出去
    emit(currentToken);
    // 开始收集属性
    return attribute;
  } else if (char === ">") {
    // 开始标签结束了
    emit(currentToken);
    emit({ type: Punctuator, value: ">" });
    // 找到>后继续收集
    return foundRightParentheses;
  }
  throw TypeError("Error");
}

function attribute(char) {
  // char=i
  if (LETTER.test(char)) {
    currentToken.type = AttributeKey;
    currentToken.value += char;
    // 继续收集属性
    return attributeKey;
  }
  throw TypeError("Error");
}

function attributeKey(char) {
  // char=d
  if (LETTER.test(char)) {
    currentToken.value += char;
    // 继续收集
    return attributeKey;
  } else if (char === "=") {
    emit(currentToken);
    // 继续收集属性值
    return attributeValue;
  }
  throw TypeError("Error");
}

function attributeValue(char) {
  // cahr="
  if (char === '"') {
    currentToken.type = AttributeStringValue;
    currentToken.value += char;
    // 开始收集字符串属性值
    return attributeStringValue;
  }
  throw TypeError("Error");
}

function attributeStringValue(char) {
  // char=title
  if (LETTER.test(char)) {
    currentToken.value += char;
    return attributeStringValue;
  } else if (char === '"') {
    emit(currentToken);
    return tryLeaveAttribute;
  }
  throw TypeError("Error");
}

// 属性结束后,可能有新属性,也可以是 >
function tryLeaveAttribute(char) {
  if (char === " ") {
    // 如果是空格的话,说明后面还有一个新属性,再次开始收集属性
    return attribute;
  } else if (char === ">") {
    // 如果是 >,则继续收集>
    emit({ type: Punctuator, value: ">" });
    // 找到 > 后继续收集
    return foundRightParentheses;
  }
  throw TypeError("Error");
}

function foundRightParentheses(char) {
  if (char === "<") {
    emit({ type: Punctuator, value: "<" });
    // 找到 < 之后继续收集
    return foundLeftParentheses;
  } else {
    currentToken.type = JSXText;
    currentToken.value += char;
    return jsxText;
  }
}

function jsxText(char) {
  if (char === "<") {
    emit(currentToken);
    emit({ type: Punctuator, value: "<" });
    return foundLeftParentheses;
  } else {
    currentToken.value += char;
    return jsxText;
  }
}

function emit(token) {
  // 每次发射 token 后就将其加入到 tokens 数组中,并将 currentToken 清空
  currentToken = { type: "", value: "" };
  tokens.push(token);
}

// 测试用例
let sourceCode = `<h1 id="title"><span>hello</span>world</h1>`; // 要分词的 jsx
console.log(tokenlizier(sourceCode));

module.exports = {
  tokenlizier,
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剑九 六千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值