经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
【编译原理】手工打造词法分析器
来源:cnblogs  作者:大数据王小皮  时间:2024/3/29 8:45:53  对本文有异议

难点:

  • 如何拆词?如何定义分隔符?
  • 匹配的优先级是什么?

关键点:

  • 有限自动机
  • 元素拆分

解析 age >= 45

为了入门字词是如何拆分识别的,我们举一个最简单的例子age >= 45

  • 只有三种类型:标识符(age)、大于号(GE)、数字字面量(IntLiteral)
  • 使用空格分隔不同的元素

思路:

  • 从左到右依次读取字符串
  • 使用有限自动机,根据读到的字符进行状态转换,状态机如下

image.png

先上代码,理解一下上述过程,也可以调试进去看看执行的逻辑是什么样的。
SimpleToken.java

  1. /**
  2. * Token的一个简单实现。只有类型和文本值两个属性。
  3. */
  4. public final class SimpleToken implements Token {
  5. //Token类型
  6. public TokenType type = null;
  7. //文本值
  8. public String text = null;
  9. @Override
  10. public TokenType getType() {
  11. return type;
  12. }
  13. @Override
  14. public String getText() {
  15. return text;
  16. }
  17. }
  18. public interface Token{
  19. public TokenType getType();
  20. public String getText();
  21. }

SimpleTokenReader

  1. public class SimpleTokenReader implements TokenReader {
  2. List<Token> tokens = null;
  3. int pos = 0;
  4. public SimpleTokenReader(List<Token> tokens) {
  5. this.tokens = tokens;
  6. }
  7. @Override
  8. public Token read() {
  9. if (pos < tokens.size()) {
  10. return tokens.get(pos++);
  11. }
  12. return null;
  13. }
  14. @Override
  15. public Token peek() {
  16. if (pos < tokens.size()) {
  17. return tokens.get(pos);
  18. }
  19. return null;
  20. }
  21. @Override
  22. public void unread() {
  23. if (pos > 0) {
  24. pos--;
  25. }
  26. }
  27. @Override
  28. public int getPosition() {
  29. return pos;
  30. }
  31. @Override
  32. public void setPosition(int position) {
  33. if (position >=0 && position < tokens.size()){
  34. pos = position;
  35. }
  36. }
  37. }
  38. public interface TokenReader{
  39. public Token read();
  40. public Token peek();
  41. public void unread();
  42. public int getPosition();
  43. public void setPosition(int position);
  44. }

MyLexer.java

  1. public class MyLexer {
  2. private StringBuffer tokenText = null; //临时保存token的文本
  3. private List<Token> tokens = null; //保存解析出来的Token
  4. private SimpleToken token = null; //当前正在解析的Token
  5. public static void main(String[] args) {
  6. MyLexer lexer = new MyLexer();
  7. String script = "age >= 45";
  8. System.out.println("parse: " + script);
  9. SimpleTokenReader tokenReader = lexer.tokenize(script);
  10. dump(tokenReader);
  11. }
  12. //是否是字母
  13. private boolean isAlpha(int ch) {
  14. return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
  15. }
  16. //是否是数字
  17. private boolean isDigit(int ch) {
  18. return ch >= '0' && ch <= '9';
  19. }
  20. //是否是空白字符
  21. private boolean isBlank(int ch) {
  22. return ch == ' ' || ch == '\t' || ch == '\n';
  23. }
  24. // 有限状态机的各种状态。
  25. private enum DfaState {
  26. Initial,
  27. Id, GT, GE,
  28. IntLiteral
  29. }
  30. /**
  31. * 有限状态机进入初始状态。
  32. * 这个初始状态其实并不做停留,它马上进入其他状态。
  33. * 开始解析的时候,进入初始状态;某个Token解析完毕,也进入初始状态,在这里把Token记下来,然后建立一个新的Token。
  34. */
  35. private DfaState initToken(char ch) {
  36. if (tokenText.length() > 0) {
  37. token.text = tokenText.toString();
  38. tokens.add(token);
  39. tokenText = new StringBuffer();
  40. token = new SimpleToken();
  41. }
  42. DfaState newState = DfaState.Initial;
  43. if (isAlpha(ch)) { //第一个字符是字母
  44. newState = DfaState.Id; //进入Id状态
  45. token.type = TokenType.Identifier;
  46. tokenText.append(ch);
  47. } else if (isDigit(ch)) { //第一个字符是数字
  48. newState = DfaState.IntLiteral;
  49. token.type = TokenType.IntLiteral;
  50. tokenText.append(ch);
  51. } else if (ch == '>') { //第一个字符是>
  52. newState = DfaState.GT;
  53. token.type = TokenType.GT;
  54. tokenText.append(ch);
  55. } else {
  56. newState = DfaState.Initial; // skip all unknown patterns
  57. }
  58. return newState;
  59. }
  60. /**
  61. * 解析字符串,形成Token。
  62. * 这是一个有限状态自动机,在不同的状态中迁移。
  63. * @param code
  64. * @return
  65. */
  66. public SimpleTokenReader tokenize(String code) {
  67. tokens = new ArrayList<Token>();
  68. CharArrayReader reader = new CharArrayReader(code.toCharArray());
  69. tokenText = new StringBuffer();
  70. token = new SimpleToken();
  71. int ich = 0;
  72. char ch = 0;
  73. DfaState state = DfaState.Initial;
  74. try {
  75. while ((ich = reader.read()) != -1) {
  76. ch = (char) ich;
  77. switch (state) {
  78. case Initial:
  79. state = initToken(ch); //重新确定后续状态
  80. break;
  81. case Id:
  82. if (isAlpha(ch) || isDigit(ch)) {
  83. tokenText.append(ch); //保持标识符状态
  84. } else {
  85. state = initToken(ch); //退出标识符状态,并保存Token
  86. }
  87. break;
  88. case GT:
  89. if (ch == '=') {
  90. token.type = TokenType.GE; //转换成GE
  91. state = DfaState.GE;
  92. tokenText.append(ch);
  93. } else {
  94. state = initToken(ch); //退出GT状态,并保存Token
  95. }
  96. break;
  97. case IntLiteral:
  98. if (isDigit(ch)) {
  99. tokenText.append(ch); //继续保持在数字字面量状态
  100. } else {
  101. state = initToken(ch); //退出当前状态,并保存Token
  102. }
  103. break;
  104. default:
  105. }
  106. }
  107. // 把最后一个token送进去
  108. if (tokenText.length() > 0) {
  109. initToken(ch);
  110. }
  111. } catch (IOException e) {
  112. e.printStackTrace();
  113. }
  114. return new SimpleTokenReader(tokens);
  115. }
  116. public static void dump(SimpleTokenReader tokenReader){
  117. System.out.println("text\ttype");
  118. Token token = null;
  119. while ((token= tokenReader.read())!=null){
  120. System.out.println(token.getText()+"\t\t"+token.getType());
  121. }
  122. }
  123. }

不难理解,对吧。
无非就是在 tokenize 函数中挨个读取字符串,根据上面自动机实现的逻辑。
遇到分隔字符(如空格)就会触发 initToken 将前面读取到的字符和类型进行保存。

你可能会有疑问:
搞这么复杂干什么?按空格切分然后再字符串匹配不就行了?

确实可以实现,使用这种方式实现还更简单,但是我们想要做的是一个更通用的处理逻辑。
如果按照提议的方式,对于更复杂的字符串(比如不是空格分隔、空格不一定是分隔符、关键字保留等)那就需要更多的人工逻辑来处理,而且会越来越复杂和难以扩展,很可能一个特例导致需要推倒重来。

原文链接:https://www.cnblogs.com/shuofxz/p/18102484

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号