首页 > 其他分享 >【编译原理】手工打造词法分析器

【编译原理】手工打造词法分析器

时间:2024-03-28 19:57:35浏览次数:21  
标签:ch 编译 分析器 tokens 造词法 token tokenText Token public

难点:

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

关键点:

  • 有限自动机
  • 元素拆分

解析 age >= 45

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

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

思路:

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

image.png

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

/**
 * Token的一个简单实现。只有类型和文本值两个属性。
 */
public final class SimpleToken implements Token {
    //Token类型
    public TokenType type = null;

    //文本值
    public String text = null;


    @Override
    public TokenType getType() {
        return type;
    }

    @Override
    public String getText() {
        return text;
    }
}

public interface Token{
    public TokenType getType();
    public String getText();
}

SimpleTokenReader

public class SimpleTokenReader implements TokenReader {
    List<Token> tokens = null;
    int pos = 0;

    public SimpleTokenReader(List<Token> tokens) {
        this.tokens = tokens;
    }

    @Override
    public Token read() {
        if (pos < tokens.size()) {
            return tokens.get(pos++);
        }
        return null;
    }

    @Override
    public Token peek() {
        if (pos < tokens.size()) {
            return tokens.get(pos);
        }
        return null;
    }

    @Override
    public void unread() {
        if (pos > 0) {
            pos--;
        }
    }

    @Override
    public int getPosition() {
        return pos;
    }

    @Override
    public void setPosition(int position) {
        if (position >=0 && position < tokens.size()){
            pos = position;
        }
    }
}


public interface TokenReader{
    public Token read();
    public Token peek();
    public void unread();
    public int getPosition();
    public void setPosition(int position);
}

MyLexer.java

public class MyLexer {
    private StringBuffer tokenText = null;   //临时保存token的文本
    private List<Token> tokens = null;       //保存解析出来的Token
    private SimpleToken token = null;        //当前正在解析的Token


    public static void main(String[] args) {
        MyLexer lexer = new MyLexer();

        String script = "age >= 45";
        System.out.println("parse: " + script);
        SimpleTokenReader tokenReader = lexer.tokenize(script);
        dump(tokenReader);
    }

    //是否是字母
    private boolean isAlpha(int ch) {
        return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
    }

    //是否是数字
    private boolean isDigit(int ch) {
        return ch >= '0' && ch <= '9';
    }

    //是否是空白字符
    private boolean isBlank(int ch) {
        return ch == ' ' || ch == '\t' || ch == '\n';
    }

    // 有限状态机的各种状态。
    private enum DfaState {
        Initial,

        Id, GT, GE,

        IntLiteral
    }

    /**
     * 有限状态机进入初始状态。
     * 这个初始状态其实并不做停留,它马上进入其他状态。
     * 开始解析的时候,进入初始状态;某个Token解析完毕,也进入初始状态,在这里把Token记下来,然后建立一个新的Token。
     */
    private DfaState initToken(char ch) {
        if (tokenText.length() > 0) {
            token.text = tokenText.toString();
            tokens.add(token);

            tokenText = new StringBuffer();
            token = new SimpleToken();
        }

        DfaState newState = DfaState.Initial;
        if (isAlpha(ch)) {              //第一个字符是字母
            newState = DfaState.Id; //进入Id状态
            token.type = TokenType.Identifier;
            tokenText.append(ch);
        } else if (isDigit(ch)) {       //第一个字符是数字
            newState = DfaState.IntLiteral;
            token.type = TokenType.IntLiteral;
            tokenText.append(ch);
        } else if (ch == '>') {         //第一个字符是>
            newState = DfaState.GT;
            token.type = TokenType.GT;
            tokenText.append(ch);
        } else {
            newState = DfaState.Initial; // skip all unknown patterns
        }
        return newState;
    }


    /**
     * 解析字符串,形成Token。
     * 这是一个有限状态自动机,在不同的状态中迁移。
     * @param code
     * @return
     */
    public SimpleTokenReader tokenize(String code) {
        tokens = new ArrayList<Token>();
        CharArrayReader reader = new CharArrayReader(code.toCharArray());
        tokenText = new StringBuffer();
        token = new SimpleToken();
        int ich = 0;
        char ch = 0;
        DfaState state = DfaState.Initial;
        try {
            while ((ich = reader.read()) != -1) {
                ch = (char) ich;
                switch (state) {
                    case Initial:
                        state = initToken(ch);          //重新确定后续状态
                        break;
                    case Id:
                        if (isAlpha(ch) || isDigit(ch)) {
                            tokenText.append(ch);       //保持标识符状态
                        } else {
                            state = initToken(ch);      //退出标识符状态,并保存Token
                        }
                        break;
                    case GT:
                        if (ch == '=') {
                            token.type = TokenType.GE;  //转换成GE
                            state = DfaState.GE;
                            tokenText.append(ch);
                        } else {
                            state = initToken(ch);      //退出GT状态,并保存Token
                        }
                        break;
                    case IntLiteral:
                        if (isDigit(ch)) {
                            tokenText.append(ch);       //继续保持在数字字面量状态
                        } else {
                            state = initToken(ch);      //退出当前状态,并保存Token
                        }
                        break;
                    default:
                }
            }
            // 把最后一个token送进去
            if (tokenText.length() > 0) {
                initToken(ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return new SimpleTokenReader(tokens);
    }

    public static void dump(SimpleTokenReader tokenReader){
        System.out.println("text\ttype");
        Token token = null;
        while ((token= tokenReader.read())!=null){
            System.out.println(token.getText()+"\t\t"+token.getType());
        }
    }

}

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

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

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

标签:ch,编译,分析器,tokens,造词法,token,tokenText,Token,public
From: https://www.cnblogs.com/shuofxz/p/18102484

相关文章

  • android编译方法
    参考资料:https://blog.csdn.net/u012514113/article/details/125514512 在编译Android源码时,开始一定会初始化系统环境变量,几条熟悉的命令:sourcebuild/envsetup.shlunchxxxmake 下面是具体作用:source:用于执行一个shell脚本文件,通常用于设置环境变量或者切换到......
  • 北京理工大学操作系统 实验一 编译Linux内核
    实验一编译Linux内核实验一编译Linux内核一、实验目的二、实验内容三、实验步骤1.安装虚拟机并配置环境实验环境:2.下载并解压Linux内核源码3.配置内核编译选项4.编译并安装内核和模块5.修改GRUB配置四、实验结果及分析五、实验收获与体会Copyright©2024Squar......
  • 编译opencv: cmake编译opencv,不带版本号
    在Linux上使用cmake编译OpenCV,默认都是协议版本号的,一般会生成三个文件,一个so和两个软链接。在部分系统上移植的时候,软链接会成问题,所以需要重新编译OpenCV,解决软链接的问题。 我是使用cmake编译的,所以需要修改【OpenCVModule.cmake】文件文件位置:【opencv-4.8.0】-【cmake......
  • ffmpeg学习window下使用Visual Studio创建cpp项目添加ffmpeg源代码编译好的依赖库
    ffmpeg学习window下使用VisualStudio创建cpp项目添加ffmpeg源代码编译好的依赖库1.创建cpp项目启动VisualStudio,创建新项目选择控制台运用程序随便输入一个项目名称,点击创建,完成helloworld项目的创建编译和运行项目,按f7编译项目,按f5运行项目下次重新打开......
  • macOS 编译 openssl + libcurl
    libcurl库但是不支持https协议 现在加上openssl来支持https首先下载openssl源码https://www.openssl.org/source我这边下载的是3.0.13编译openssl参考这个https://zhuanlan.zhihu.com/p/628437266    主要命令./Configuredarwin64-x86_64-cc--prefix="/Use......
  • 实验一 密码引擎-1-OpenEuler-OpenSSL编译
    安装Ubuntu和OpenEuler虚拟机下载最新的OpenSSL源码(3.2.1版本)用自己的8位学号建立一个文件夹,cd你的学号,用pwd获得绝对路径参考https://www.cnblogs.com/rocedu/p/5087623.html先在Ubuntu中完成OpenSSL编译安装,然后在OpenEuler中重现./config--prefix=..(学号目录......
  • 使用compileall模块编译Python的源代码
    编译当前目录及子目录所有python源码为pyc文件python-mcompileall-b.参数说明-b:这个选项告诉compileall模块在编译时进行备份。它会为每个已编译的源文件创建一个备份文件,后缀为.pyc.bak.:这是命令行中的当前目录。这意味着compileall将会在当前目录及其所有子目录中......
  • FreeType编译与使用
    FreeType是一款免费用于渲染字体的开源库。在使用该类库时,最好先过一遍官方文档,其中FreeTypeGlyphConventions部分的文章必读。编译我们可以进入下载界面,点击任意一个地址下载源码。这里笔者使用的是2.13.2版本,解压后会获得一个freetype-2.13.2文件夹。进入目录freetype-2.......
  • VS、Qt编译遇到的错误
    ---1、404  NOTFOUND  downloading'http://mitk.org/download/thirdparty/DCMQI.tar.gz'failed       camke中的ep路径没有配置好---2、C4996'strcpy':Thisfunctionorvariablemaybeunsafe.Considerusingstrcpy_sinstead.Todisabledepre......
  • 分享一个项目:go `file_line`,在编译器得到源码行号,减少运行期runtime消耗
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯file_linehttps://github.com/ahfuzhang/file_lineLike__FILE__/__LINE__ofC:usegogeneratetogetsourcecodelinenumberatcompiletime.像C语言里面......