Kotlin你不知道的秘密(二)

4,658 阅读5分钟

image.png

本篇主要说一下Koltin分支中的秘密的第二个:为什么 表达式if/When要结合使用?陆续后面还有会2-3篇来介绍其他秘密,文章中提到的代码和其他资料已开源到Android知识体系& Android-Body

词法分析:Koltin关键字(final/if/for)、运算符(+/-/?:)是如何被识别的?已更新完,可以连起来阅读会更容易理解本篇内容。

Kotlin表达式if/When为什么要结合使用?

其实这个问题可以理解为输入的每个单词组合起来在结构上是否是正确的语句?简称:语法分析。上篇文章讲述了词法分析,如果把词法分析看作为字母组合成单词的过程,那么语法分析就是一个把单词组合成句子的过程。

语法分析:语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确 -- 百科 怎么理解输入的单词组合是不是一句正确的话语句那?那举个栗子:

  • 小红:我们书桌空气。
  • 小明:*** 小明为什么要说*** 因为小明不知道你在说什么他不理解你说的是不是一句话而且在结构式也构不成一句话。何为一句正确的话?我们看到小红说的话是有多个词语组成的,词语之间没有任何连贯也组合不成句子所有小明就很蒙又加上他最近可能大姨夫来了所以才表现的比较亢奋。但是程序是没有情绪科可言的,不管你怎么对他,他都是不冷不热的样子。比如我们常见的:

1617085964(1).jpg

可以看到我们只输入一个if表达式,studio会给我们一个错误提示,这个提示什么意思那?大概可以理解为期望一个()并在括号在输入条件,我们按照他的提示我们输入括号

1617086431(1).jpg

他又是提示我们期待一个表达式,从这个时候看到它不像小明那样很暴躁直接来句***。而是会一步一步提示我们它想要的是什么,而当你输入表达式后它又说没有内容,它希望你输入内容来起到他的作用。感兴趣的可以一步一步尝试下。这时候可以我就有一个疑问:他为什么能知道他想要的? 因为他有语法分析器。

语法分析器

public class KotlinParser implements PsiParser {

    @NotNull
    public ASTNode parse(IElementType iElementType, PsiBuilder psiBuilder, PsiFile psiFile) {
        KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
        String extension = FileUtilRt.getExtension(psiFile.getName());
        if (extension.isEmpty() || extension.equals(KotlinFileType.EXTENSION) || (psiFile instanceof KtFile && ((KtFile) psiFile).isCompiled())) {
            ktParsing.parseFile();
        } else {
            ktParsing.parseScript();
        }
        return psiBuilder.getTreeBuilt();
    }
}

可以看到Kotlin的语法分析是通过KotlinParsing进行的,在parse方法中创建KotlinParsing然后把要分析的文件传递过去,这里的文件也就是我们熟悉的以 .kt结尾的文件。当然也不局限于kt文件。接着我们跟进下ktParsing.parseFile();

public class KotlinParsing extends AbstractKotlinParsing {
    void parseFile() {
        PsiBuilder.Marker fileMarker = mark();
        //分析类的注释、package、import*
        parsePreamble();
        while (!eof()) {
            //分析包、类、方法的声明方法,例如:package、class、function
            parseTopLevelDeclaration();
        }
        checkUnclosedBlockComment();
        fileMarker.done(KT_FILE);
    }
}

parseFile中做的.kt类中的声明分析,这些声明也就是上一篇提到的KtTokens中定义的关键字和操作符之类的。顺便提一下语法分析大概分为两类:

自顶向下分析:根据形式语法规则,在语法分析树的自顶向下展开中搜索输入符号串可能的最左推导。单词按从左到右的顺序依次使用。

自底向上分析:语法分析器从现有的输入符号串开始,尝试将其根据给定的形式语法规则进行改写,最终改写为语法的起始符号。

可以看到Koltin中是使用的自顶向下分析法,这种分析法也比较适合我们的编码习惯。 到这基本上有了语法分析的大概,但是没有看到我们前面举例的if和括号使用的分析。其实在KotlinParser中会创建KotlinExpressionParsing类,这个类中主要处理的也是一些和表达式相关的分析。

public class KotlinExpressionParsing extends AbstractKotlinParsing {

    private boolean parseAtomicExpression() {
        boolean ok = true;
        if (at(LPAR)) {
            parseParenthesizedExpression();
        }else if (at(LBRACKET)) {
            parseCollectionLiteralExpression();
        } else if (at(IF_KEYWORD)) {
            parseIf();
        }
        ....
        return ok;
    }
    
    private void parseIf() {
        advance();
        parseCondition();
        PsiBuilder.Marker thenBranch = mark();
        if (!at(ELSE_KEYWORD) && !at(SEMICOLON)) {
            parseControlStructureBody();
        }
        if (at(SEMICOLON) && lookahead(1) == ELSE_KEYWORD) {
            advance(); // SEMICOLON
        }
        thenBranch.done(THEN);
    }
    
    private void parseCondition() {
        myBuilder.disableNewlines();
        if (expect(LPAR, "Expecting a condition in parentheses '(...)'", EXPRESSION_FIRST)) {
            PsiBuilder.Marker condition = mark();
            parseExpression();
            condition.done(CONDITION);
            expect(RPAR, "Expecting ')");
        }
        myBuilder.restoreNewlinesState();
    }
}
    

parseAtomicExpression方法中根据getTokenType判断当前关键字是this、try、if、when等,然后对单个关键字进行分析,以parseIf为例会先通过parseCondition检验if后有没有LPAR KtSingleValueToken LPAR = new KtSingleValueToken("LPAR", "(");LPAR也就是我们在Token定义的“(”,如果发现没有括号也就会给我们提示前面我再studio中写if语句中的错误信息Expecting a condition in parentheses '(...)'然后进行后续的判断,直到满足语法的要求,是一个完整的句子才不会给错误的提示。当然没有错误后会回到KotlinParser中的psiBuilder.getTreeBuilt();并生成AST抽象语法树

抽象语法树

PsiBuilder.gif

抽象语法树的建立过程如图所示,可以让你更加清晰的理解本篇内容,图中的关键方法和类我们也有提到过。每一步骤的具体介绍和关键方法的作用IntelliJ平台插件SDK都有介绍,我就不做代码的搬运工了,感兴趣的可以了解下

总结

我们知道语法分析器是在词法分析之后,根据词法分析的结果和定义的语法规则判断输入的程序是否有语法错误。知其表也要知其里 综合上面的知识我们就可以得出Kotlin表达式if/When为什么要结合使用?大概分为3步如下:

1、我们拿到词法分析后输出在KtTokens中定义的关键字和运算符等如:if()、for()、final等 (获取Token流)
2、KotlinParsing会根据Token中定义的关键字进行组合判断在结构上是正确,如果在结构组合上不符合规范就抛出异常如:Expecting a condition in parentheses '(...)'(KotlinParsing语法分析)
3、符合规范后会生成AST抽象语法树 (生成抽象语法树)