摘要:即创建一个文件,并写下如下内容。然后以此缓存一个字符串,然后,通过调用来将这个字符串分割成一个一个运算符,并生成。语言中根据运算符长度分开储存的。具体来说,如果运算符对应的字符串最短的是,最长的是。
上一章留下的那个大大的 TODO 表明,似乎还有什么东西没写完。没错,所以这一章我们来写 Sign 状态下的代码。
所谓 Sign 状态,即是用来处理和生成 Sign 类型的 Token ,即运算符。诸如此类,都是运算符:
+、-、*、/、>=、==、&&、||
相对于 String 字符串啊、Identifier 标示符之类的可以通过字符本身来划分开端和结尾的类型,Sign 运算符运算符类型却只能以既定形式来划分开端和结尾。
例如,
"hello world" +-&& str_hello_wold
这段“源码”可以被分割成如下一组 Token,
[""hello world"", " ", "+", "-", "&&", "str_hello_world"]
注意 Sign 运算符类型和 String 字符串类型、Identifier 标示符划分的区别。对于 “+”、“-” 两个字符被各自分在一个 Token 中,而 “&&” 却是由两个字符组合成一个 Token。因为 tao 语言中没有 “&” 运算符(没有与位运算,也没有短路与非短路与之分),却拥有 “&&”。
此外,
>=
对于这个字符串,到底应该划分成
[">", "="]
这样两个运算符呢,还是应该划分成
[">="]
这样一个运算符呢?
因为 “>"、"="、">=" 这三个都是合法的 tao 语言运算符。这里为了消除歧义,我规定必须划分成 ">=" 。另外,如果存在多种划分方式,尽量将短的运算符凑成长运算符。
OK,我现在决定把有关 Sign 运算符类型的代码多带带写一个类。即创建一个 SignParser.java 文件,并写下如下内容。
package com.taozeyu.taolan.analysis; class SignParser { static boolean inCharSet(char c) { //TODO } static Listparse(String str) { //TODO } }
对于,LexicalAnalysis.java ,它只会调用 SignParser.inCharset(...) 来判断当前读入的字符是不是某个既定运算符的某个字符。然后以此缓存一个字符串,然后,通过调用 SignParser.parse(str) 来将这个字符串分割成一个一个运算符,并生成 Token。
现把 LexicalAnalysis.java 中 boolean readChar(char c) 函数中TODO 消灭掉吧。
} else if(state == State.Sign) { if(SignParser.inCharSet(c)) { readBuffer.append(c); } else { Listlist = SignParser.parse(readBuffer.toString()); for(String signStr:list) { createToken(Type.Sign, signStr); } createType = null; state = State.Normal; moveCursor = false; } }
当然,这里需要重载 createToken 一下。
private void createToken(Type type, String value) { Token token = new Token(type, value); tokenBuffer.addFirst(token); readBuffer = null; }
OK,我们在回过头来写 SignParser.java 吧。首先,定义几个静态常量。
package com.taozeyu.taolan.analysis; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; class SignParser { private final static List> signSetList; private final static HashSet signCharSet; private final static int MaxLength, MinLength; static { String[] signArray = new String[] { "+", "-", "*", "/", "%", ">", "<", ">=", "<=", "=", "!=", "==", "=~", "+=", "-=", "*=", "/=", "%=", "&&", "||", "!", "^", "&&=", "||=", "^=", "<<", ">>", "->", "<-", "?", ":", ".", ",", ";", "..", "(", ")", "[", "]", "{", "}", "@", "@@", "$", }; int maxLength = Integer.MIN_VALUE, minLength = Integer.MAX_VALUE; signCharSet = new HashSet<>(); for(String sign:signArray) { int length = sign.length(); if(length > maxLength) { maxLength = length; } if(length < minLength) { minLength = length; } for(int i=0; i (maxLength - minLength + 1); for(int i=0; i< maxLength - minLength + 1; ++i) { signSetList.add(new HashSet<>()); } for(String sign:signArray) { int length = sign.length(); HashSet signSet = signSetList.get(length - minLength); signSet.add(sign); } MaxLength = maxLength; MinLength = minLength; } static boolean inCharSet(char c) { //TODO } static List parse(String str) { //TODO } }
static 块中的 signArray 变量定义了 tao 语言所用到的所有运算符。之后的代码解析 signArray 并生成 4 个静态常量。
signCharSet:tao 语言所有运算符中可能出现的字符集合。
signSetList:tao 语言中根据运算符长度分开储存的 List。具体来说,如果运算符对应的字符串最短的 length 是 n,最长的是 m。那么 List 的索引范围从 0 到 (m - n) 分别储存字符 length 从 n 到 m 的所有运算符。
MinLength:运算符中字符串 length 的最小长度。
MaxLength:运算符中字符串 length 的最大长度。
然后是填满那两个写着 TODO 的函数。
static boolean inCharSet(char c) { return signCharSet.contains(c); } static Listparse(String str) throws LexicalAnalysisException { LinkedList rsContainer = new LinkedList<>(); int startIndex = 0; while(startIndex < str.length()) { String matchStr = match(startIndex, str); if(matchStr == null) { throw new LexicalAnalysisException(str.substring(startIndex)); } else { rsContainer.add(matchStr); startIndex += matchStr.length(); } } return rsContainer; } private static String match(int startIndex, String str) { String matchStr = null; int length = str.length() - startIndex; length = Math.min(length, MaxLength); if(length >= MinLength) { for(int i=length - MinLength; i>=0; i--) { int matchLength = i + MinLength; HashSet signSet = signSetList.get(i); matchStr = str.substring(startIndex, startIndex + matchLength); if(signSet.contains(matchStr)) { break; } matchStr = null; } } return matchStr; }
OK,搞定。特别的,如果 parse 划分运算符过程中发现留下一个尾巴匹配不了,那么将抛出一个词法错误异常。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64265.html
摘要:是的,这个系列将呈现一个完整的编译器从无到有的过程。但在写这个编译器的过程中,我可不会偷工减料,该有的一定会写上的。该语言的虚拟机将运行于之上,同时编译器将使用实现。我早有写编译器的想法之前没写过,故希望一边写编译器一边完成这个系列。 是的,这个系列将呈现一个完整的编译器从无到有的过程。当然,为了保证该系列内容的简洁(也为了降低难度),仅仅保证编译器的最低要求,即仅能用。但在写这个编译...
摘要:在之前的章节第章从零开始写个编译器吧开始写词法分析器中我有说,我将函数设计成主动调用的形式,而则是被动调用的形式。接下来本系列将进入编写语法分析器的阶段,不过在此之前,我将抽出一点时间介绍一下语言本身。 上周周末旅游去了,就没更新了,虽然回到海拔0m的地区,不过目前似乎还在缺氧,所以本次就少更点吧。 这章将结束词法分析的部分。 在之前的章节(第7章从零开始写个编译器吧 - 开始写词...
摘要:要为语言设计词法分析器,首先得知道语言是一种什么样的语言。,不过首先我们得把词法分析器能生成的单词类型定义好了。 要为 tao 语言设计词法分析器,首先得知道 tao 语言是一种什么样的语言。不过呢,我脑海里还没有 tao 语言具体形象。我还是先贴一段 tao 语言的代码,大概展示下这是怎么回事吧。 def say_hello_world(who) print hello ...
摘要:现在,让我们来动手写编译器的第一个个文件吧。如其名字所示,这个类实例化的对象用于表示词法分析器的产物。我希望词法分析器从源代码中提取出语素,并根据上下文推测出单词类型,从而构造出对象。只需要构造出类型即可,进一步细分将在的构造函数中进行。 现在,让我们来动手写编译器的第一个个java文件吧。本章要写的类,是Token类。如其名字所示,这个类实例化的对象用于表示词法分析器 Tokeniz...
摘要:这样的程序或称工具有很多现成的可供选择包括在平台上可用的,但既然我这个系列叫做从零开始写个编译器吧,那显然如果我用现成的工具,那是犯规行为。 Parser(语法分析器)的编写相对于 Tokenizer (词法分析器)要复杂得多,因此,在编写之前可能也会铺垫得更多一些。当然,本系列旨在写出一个编译器,所以理论方面只会简单介绍 tao 语言所涉及的部分。 之前的几章中,我纯手写了tao 语...
阅读 1744·2021-11-24 09:39
阅读 1512·2021-11-16 11:54
阅读 3475·2021-11-11 16:55
阅读 1611·2021-10-14 09:43
阅读 1421·2019-08-30 15:55
阅读 1213·2019-08-30 15:54
阅读 3403·2019-08-30 15:53
阅读 1311·2019-08-30 14:18