资讯专栏INFORMATION COLUMN

从零开始写个编译器吧 - 开始写词法分析器(1)

littleGrow / 2841人阅读

摘要:上一章提到我要手写词法分析器这个状态机,嗯,那就让我们开始吧。实际上,在状态机不断接受字符的过程中,会先调用将其缓存,并在适当的时机调用生成。一个典型的状态机,处于不同状态,对于接受的参数进行不同的操作。

上一章提到我要手写词法分析器这个状态机,嗯,那就让我们开始吧。

    public class LexicalAnalysis {
        
        private static enum State {
            Normal, 
            Identifier, Sign, Annotation,
            String, RegEx, Space;
        }
    
        public LexicalAnalysis(Reader reader) {
            //TODO
        }
    
        Token read() throws IOException, LexicalAnalysisException {
            //TODO
            return null;
        }

        private State state;
        private final LinkedList tokenBuffer = new LinkedList<>();
        private StringBuilder readBuffer = null;
        
        private void refreshBuffer(char c) {
            readBuffer = new StringBuilder();
            readBuffer.append(c);
        }

    private void createToken(Type type) {
        Token token = new Token(type, readBuffer.toString());
        tokenBuffer.addFirst(token);
        readBuffer = null;
    }
        
        private boolean readChar(char c) throws LexicalAnalysisException {
            //TODO 状态机逻辑...
        }
    }

于是我又添加了一点代码。可以看到,我放着 readChar() 函数的 TODO 不管,又添加了一个 readChar(char c) 的函数。因为我有这么一个考虑:对于readChar()方法而言,这个是一个被动调用的函数,外部调用一下,就读到一个Token。这样设计,今后写 Parser 时读取 Token 会要简单许多。而readChar(char c)是一个主动调用的函数,它的实现部分直接处理接受的参数 char 就行了,而且也不必立即返回 Token。这样我在写 readChar(char c) 本身的时候会简单许多。

实际上,在状态机不断接受字符的过程中,会先调用 readBuffer.append(...) 将其缓存,并在适当的时机调用 createToken(...) 生成 Token。

至此,readChar(char c) 就变成了一个纯粹用于实现状态机功能的函数了。让我们开始写这个函数吧。

    private State state;

    private boolean readChar(char c) throws LexicalAnalysisException {
    
        boolean moveCursor = true;
        Type createType = null;
    
        if(state == State.Normal) {
            
        } else if(state == State.Identifier) {
            
        } else if(state == State.Sign) {
        
        } else if(state == State.Annotation) {
            
        } else if(state == State.String) {
            
        } else if(state == State.RegEx) {
        
        } else if(state == State.Space) {
            
        }
        
        if(createType != null) {
            createToken(createType);
        }
        return moveCursor;
    }

一个典型的状态机,处于不同状态,对于接受的参数 char 进行不同的操作。同时,我可以通过 state = ?; 来改变状态机的状态。

这个函数会返回一个 boolean 类型的变量,即 moveCursor,这个变量表示,在读完当前 char 之后,是否移动游标读取下一个字符。如果为 false,则该函数的下一次调用的参数与前一次调用的参数会一模一样(因为游标没有移动嘛)。

最自然的情况下 moveCursor == true,就是读了一个字符以后再读下一个字符嘛。

嗯,然后开始填充这些 if-else 之间的空白吧,先从 Normal 状态开始。

private boolean readChar(char c) throws LexicalAnalysisException {

    boolean moveCursor = true;
    Type createType = null;
    
    if(state == State.Normal) {
        if(inIdentifierSetButNotRear(c)) {
            state = State.Identifier;
        }
        else if(SignParser.inCharSet(c)) {
            state = State.Sign;
        }
        else if(c == "#") {
            state = State.Annotation;
        }
        else if(c == """ | c == """) {
            state = State.String;
        }
        else if(c == "`") {
            state = State.RegEx;
        }
        else if(include(Space, c)) {
            state = State.Space;
        }
        else if(c == "
") {
            createType = Type.NewLine;
        }
        else if(c == "") {
            createType = Type.EndSymbol;
        }
        else {
            throw new LexicalAnalysisException(c);
        }
        refreshBuffer(c);
        
    } else if(state == State.Identifier) {
        
    } else if(state == State.Sign) {
        
    } else if(state == State.Annotation) {
        
    } else if(state == State.String) {
        
    } else if(state == State.RegEx) {
        
     } else if(state == State.Space) {
        
    }
        
    if(createType != null) {
        createToken(createType);
    }
    return moveCursor;
}

填充的代码中出现了一些新函数和变量,我将补充在下面。

    private static final char[] Space = new char[] {" ", "	"};
    
    private boolean inIdentifierSetButNotRear(char c) {
        return (c >= "a" & c <= "z" ) | (c >="A" & c <= "Z") | (c >= "0" & c <= "9")|| (c == "_");
    }
    
    private boolean include(char[] range, char c) {
        boolean include = false;
        for(int i=0; i

其中 include 函数用于判断字符是否归属于某个集合。而 inIdentifierSetButNotRear 用于判定字符是否属于标示符字符。这里的标示符不仅限于用户定义的标示符,还包括关键字和数字(第5章有提及)。

当然,还有一个 SignParser.inCharSet(c) ,不过这个我想推迟到今后的章节说明,此处就不多做展开。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64211.html

相关文章

  • 从零开始译器系列

    摘要:是的,这个系列将呈现一个完整的编译器从无到有的过程。但在写这个编译器的过程中,我可不会偷工减料,该有的一定会写上的。该语言的虚拟机将运行于之上,同时编译器将使用实现。我早有写编译器的想法之前没写过,故希望一边写编译器一边完成这个系列。 是的,这个系列将呈现一个完整的编译器从无到有的过程。当然,为了保证该系列内容的简洁(也为了降低难度),仅仅保证编译器的最低要求,即仅能用。但在写这个编译...

    genedna 评论0 收藏0
  • 从零开始译器 - 开始词法析器(3)

    摘要:在之前的章节第章从零开始写个编译器吧开始写词法分析器中我有说,我将函数设计成主动调用的形式,而则是被动调用的形式。接下来本系列将进入编写语法分析器的阶段,不过在此之前,我将抽出一点时间介绍一下语言本身。 上周周末旅游去了,就没更新了,虽然回到海拔0m的地区,不过目前似乎还在缺氧,所以本次就少更点吧。 这章将结束词法分析的部分。 在之前的章节(第7章从零开始写个编译器吧 - 开始写词...

    Barrior 评论0 收藏0
  • 从零开始译器 - tao语言的词法析器(Tokenizer)的类型定义

    摘要:要为语言设计词法分析器,首先得知道语言是一种什么样的语言。,不过首先我们得把词法分析器能生成的单词类型定义好了。 要为 tao 语言设计词法分析器,首先得知道 tao 语言是一种什么样的语言。不过呢,我脑海里还没有 tao 语言具体形象。我还是先贴一段 tao 语言的代码,大概展示下这是怎么回事吧。 def say_hello_world(who) print hello ...

    qpal 评论0 收藏0
  • 从零开始译器 - 词法析器是一个状态机

    摘要:词法分析器本身就是一个状态机,生成这个状态机有很多种方法,而我打算采取手写的方式。状态机不断从源代码即一个字符串中读入一个一个字符,读到不同的字符将使状态机的状态从一个状态变化到另外一个状态。 词法分析器 Tokenizer 本身就是一个状态机,生成这个状态机有很多种方法,而我打算采取手写的方式。因为 tao 语言的词法还是相对比较简单的,手写不成问题。 先新建一个LexicalAna...

    calx 评论0 收藏0
  • 从零开始译器 - 单词化简述(Tokenization)

    摘要:实际上,所谓的源代码,我们可以将其视为一段长长的字符串。但仅仅是把源代码的字符分割成段,这些字符串尚不能称之为完整的单词,而只能作为单词的语素。实际上,词法分析器还对将单词分类。实际上,词法分析器会为这行代码生成如下形式。 实际上,所谓的源代码,我们可以将其视为一段长长的字符串。所谓字符串,即是字符的有序集。但是,字符本身作为编译器的输入单位,粒度实在太小了,因此,我们往往需要对编译器...

    lucas 评论0 收藏0

发表评论

0条评论

littleGrow

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<