资讯专栏INFORMATION COLUMN

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

MarvinZhang / 749人阅读

摘要:读到一个非数字非英文字母非下划线字符。此时立即跳转回状态。以一个双引号开始,并以一个双引号结束。另外,在读和时源代码不许结束,即读到符号,若结束,则判定为词法错误。对于而言,也有一些其他的词法错误判定,如,不能换行。

对于 Normal 状态,我只需要关心两个过程:

何时从 Normal 跳转到该状态

何时从该状态跳回 Normal 状态。

在上一章中,我已经写好了从 Normal 状态跳转到各个其他状态的代码,这一章中,我将写完所有非 Normal 状态下对字符的处理代码,以及跳回 Normal 状态代码。

首先是 Identifier 标示符:

回顾上一章,Normal 状态在何时会跳转到 Identifier 状态呢?

if(state == State.Normal) {
    if(inIdentifierSetButNotRear(c)) {
        state = State.Identifier;
    }
}

而身处 Identifier 状态时。

else if(state == State.Identifier) {

    if(inIdentifierSetButNotRear(c)) {
        readBuffer.append(c);

    } else if(include(IdentifierRearSign, c)) {
        createType = Type.Identifier;
        readBuffer.append(c);
        state = State.Normal;

    } else {
        createType = Type.Identifier;
        state = State.Normal;
        moveCursor = false;
    }
}

这段代码用到了 IdentifierRearSign ,我它的定义贴在下面把。

private static final char[] IdentifierRearSign = new char[] {"?", "!"};

以上这段代码表明。处于 Normal 状态时,读到数字、英文字母、下划线时,会跳转到 Identifier 状态。之后如果继续读数字、英语字母、下划线,则会缓存这些字符,并继续保持 Identifier 状态,直到:

读到 “?”,“!” 这两个只能用于 Identifier 结尾部分的字符,则立即创建一个 Identifier 的 Token 并跳转回 Normal 状态。

读到一个非数字、非英文字母、非下划线字符。此时立即跳转回 Normal 状态。但不移动游标,以便令处于 Normal 状态下的代码来判断这个字符属于什么样的 Token。

然后是 Annotation 注释:

回顾上一章,Normal 状态何时跳转到 Annotation 状态呢?

if(state == State.Normal) {
    ...
    else if(c == "#") {
        state = State.Annotation;
    }
}

处于 Annotation 状态时的代码如下。

else if(state == State.Annotation) {

    if(c != "
" & c != "") {
        readBuffer.append(c);

    } else {
        createType = Type.Annotation;
        state = State.Normal;
        moveCursor = false;
    }
}

注释 Annotation 自 “#” 符号开始,读到换行符(或源代码读完了)则结束。特别的,注释结束后不移动游标,因为读到的最后一个字符要由跳转回的 Normal 做处理。(往往是生成一个 NewLine 类型的 Token 或 EndSymbol 类型的 Token。)

之后是 String 字符串、 RegEx 正则表达式:

回顾上一章,Normal 状态何时跳转到这两种状态呢?

if(state == State.Normal) {
    ...
    else if(c == """ | c == """) {
        state = State.String;
    }
    else if(c == "`") {
        state = State.RegEx;
    }
}

而处于 String、RegEx 状态下的代码如下。

 else if(state == State.String) {

    if(c == "
") {
        throw new LexicalAnalysisException(c);

    } else if(c == "") {
        throw new LexicalAnalysisException(c);

    } else if(transferredMeaningSign) {

        Character tms = StringTMMap.get(c);
        if(tms == null) {
            throw new LexicalAnalysisException(c);
        }
        readBuffer.append(tms);
        transferredMeaningSign = false;

    } else if(c == "") {
        transferredMeaningSign = true;

    } else {
        readBuffer.append(c);
        char firstChar = readBuffer.charAt(0);
        if(firstChar == c) {
            createType = Type.String;
            state = State.Normal;
        }
    }
} else if(state == State.RegEx) {

    if(transferredMeaningSign) {

        if(c != "`") {
            throw new LexicalAnalysisException(c);
        }
        readBuffer.append(c);
        transferredMeaningSign = false;

    } else if(c =="") {
        transferredMeaningSign = true;

    } else if(c == "") {
        throw new LexicalAnalysisException(c);

    } else if(c == "`") {
        readBuffer.append(c);
        createType = Type.RegEx;
        state = State.Normal;

    } else {
        readBuffer.append(c);
    }
} 

当然,这里引入了一个新变量(成员变量),其声明如下。这个变量用于处理转义符号“”。

private boolean transferredMeaningSign;

当然,这个变量必须在从 Normal 状态跳转到 String、RegEx 状态时初始化值。因此 Normal 状态下的代码也要做少许修改。

if(state == State.Normal) {
    ...
    else if(c == """ | c == """) {
        state = State.String;
        transferredMeaningSign = false;
    }
    else if(c == "`") {
        state = State.RegEx;
        transferredMeaningSign = false;
    }
}

所谓转义,举个例子。字符串可以写成 "hello world." 这种形式。以一个双引号开始,并以一个双引号结束。加入我要在字符串中间出现双引号,则必须使用转义符号。例如,"he said "hello world"." 这样的形式。

特别的,一些特殊不可见字符也可以用转义符号表示,例如, 、 分别表示换行符、制表符。对于这些符号的映射关系,我建立了一张 HashMap 来表示。

private static final HashMap StringTMMap = new HashMap<>();

static {
    StringTMMap.put(""", """);
    StringTMMap.put(""", """);
    StringTMMap.put("", "");
    StringTMMap.put("b", "");
    StringTMMap.put("f", "f");
    StringTMMap.put("t", "	");
    StringTMMap.put("r", "
");
    StringTMMap.put("n", "
");
}

因为 String 和 RegEx 都有明显的结束符号,因此只需要将读取的字符缓存,并在读到结束符号时生成对应的 Token,并跳回 Normal 状态即可。

只不过因为存在转义符号这种东西,所以要特别处理一下。

另外,在读 String 和 RegEx 时源代码不许结束,即读到 "" 符号,若结束,则判定为词法错误。当然,转义奇奇怪怪的东西也是词法错误。对于 String 而言,也有一些其他的词法错误判定,如,不能换行。

最后,Space 空白:

回顾上一章,Normal 状态到 Space 状态的代码。

else if(include(Space, c)) {
    state = State.Space;
}

而 Space 状态下的代码。

} else if(state == State.Space) {

    if(include(Space, c)) {
        readBuffer.append(c);

    } else {
        createType = Type.Space;
        state = State.Normal;
        moveCursor = false;
    }
}

此处无需多言。

最后的最后,还有一些 Normal 状态下不必跳转状态即可处理掉的状况:

else if(c == "
") {
    createType = Type.NewLine;
}
else if(c == "") {
    createType = Type.EndSymbol;
}

即 NewLine 换行符和 EndSymbol 终止符。也无需多言。

上一张和本章所写的全部代码:

package com.taozeyu.taolan.analysis;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.LinkedList;

import com.taozeyu.taolan.analysis.Token.Type;

public class LexicalAnalysis {

    private static enum State {
        Normal, 
        Identifier, Sign, Annotation,
        String, RegEx, Space;
    }

    private static final char[] IdentifierRearSign = new char[] {"?", "!"};
    private static final char[] Space = new char[] {" ", "	"};

    private static final HashMap StringTMMap = new HashMap<>();

    static {
        StringTMMap.put(""", """);
        StringTMMap.put(""", """);
        StringTMMap.put("", "");
        StringTMMap.put("b", "");
        StringTMMap.put("f", "f");
        StringTMMap.put("t", "	");
        StringTMMap.put("r", "
");
        StringTMMap.put("n", "
");
    }

    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 boolean transferredMeaningSign = false;

    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 {

        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;
                transferredMeaningSign = false;
            }
            else if(c == "`") {
                state = State.RegEx;
                transferredMeaningSign = false;
            }
            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) {

            if(inIdentifierSetButNotRear(c)) {
                readBuffer.append(c);

            } else if(include(IdentifierRearSign, c)) {
                createType = Type.Identifier;
                readBuffer.append(c);
                state = State.Normal;

            } else {
                createType = Type.Identifier;
                state = State.Normal;
                moveCursor = false;
            }
        } else if(state == State.Sign) {        
            //TODO

        } else if(state == State.Annotation) {

            if(c != "
" & c != "") {
                readBuffer.append(c);

            } else {
                createType = Type.Annotation;
                state = State.Normal;
                moveCursor = false;
            }
        } else if(state == State.String) {

            if(c == "
") {
                throw new LexicalAnalysisException(c);

            } else if(c == "") {
                throw new LexicalAnalysisException(c);

            } else if(transferredMeaningSign) {

                Character tms = StringTMMap.get(c);
                if(tms == null) {
                    throw new LexicalAnalysisException(c);
                }
                readBuffer.append(tms);
                transferredMeaningSign = false;

            } else if(c == "") {
                transferredMeaningSign = true;

                } else {
                readBuffer.append(c);
                char firstChar = readBuffer.charAt(0);
                if(firstChar == c) {
                    createType = Type.String;
                    state = State.Normal;
                }
            }
        } else if(state == State.RegEx) {

            if(transferredMeaningSign) {

                if(c != "`") {
                    throw new LexicalAnalysisException(c);
                }
                readBuffer.append(c);
                transferredMeaningSign = false;

            } else if(c =="") {
                transferredMeaningSign = true;

            } else if(c == "") {
                throw new LexicalAnalysisException(c);

            } else if(c == "`") {
                readBuffer.append(c);
                createType = Type.RegEx;
                state = State.Normal;

            } else {
                readBuffer.append(c);
            }

        } else if(state == State.Space) {

            if(include(Space, c)) {
                readBuffer.append(c);

            } else {
                createType = Type.Space;
                state = State.Normal;
                moveCursor = false;
            }
        }

        if(createType != null) {
            createToken(createType);
        }
        return moveCursor;
    }

    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           
               
                                           
                       
                 

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

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

相关文章

  • 从零开始译器系列

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

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

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

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

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

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

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

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

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

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

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

    lucas 评论0 收藏0

发表评论

0条评论

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