概述
问候,
今天我们有工作要做:我们将设计和实施第一部分
我们的小编译器。
代币我们从标记化部分开始; 我们的孩子认可的代币
语言有点像C或Java令牌:我们想识别以下内容
二进制运算符:+-* / ^(最后一个代表“提升为幂”),
和
比较运算符:<<= ==!= =>>以及一些一元运算符:
! +-++-
我们还要识别更多字符:=(和)
最重要的是,我们想识别标识符(或“名称”),就像在
Java或C,我们想识别浮点数。
所有其他字符实际上并没有“被识别”,而只是按原样传递
解析器。 有一个例外:我们要认识到以下事实:
文本已被读取并标记为:文本条件的结尾。
用我们的简单语言会忽略空格。 令牌生成器还必须
跟踪当前行号和列号,因为我们要
就像javac编译器一样,综合漂亮的错误消息。
基本上,分词器通过以下方式对令牌进行分组:
// different types of tokens:
static final int T_ENDT= -1; // end of stream reached
static final int T_CHAR= 0; // an ordinary character
static final int T_NUMB= 1; // a number
static final int T_TEXT= 2; // a recognized token
static final int T_NAME= 3; // an identifier
我想到的第一个想法是使用Scanner进行令牌识别。
,我们无法使用它,因为扫描程序基于定界符(即令牌)工作
用分度符分隔(您可以根据需要进行设置)。 我们的
语言没有分界符,即我们不想写:'a + b'
一直以来,我们只想写“ a + b”。 我们的代币由
更改字符组,我们事先不知道我们的标记器
将要从输入流中读取,因此我们不知道要使用哪个分度。
我们采用另一种方法:我们只需逐行读取输入并使用
正则表达式以找出第一个可能的标记是什么。 如果
我们找到了一个,我们将其从行中切下并返回令牌。 如果有
不再需要阅读令牌,我们只需阅读下一行并重复该过程即可。
这里有一个小陷阱:假设流中的第一个字符是'+'
或'>'或'<'或'='符号? 我们能否确定那个单一角色
标记,还是我们也应该检查下一个字符? 如果流包含
++或> =或<=或==,那么我们确实应该检查下一个字符。
以下简单规则避免了这个小陷阱:
我们按以下顺序检查令牌:
check for an end of stream state; if found we return a T_ENDT token;
check for spaces, if found we ignore them and continue;
check for a number, if found return a T_NUMB token;
check for an identifier, if found return a T_NAME token;
check for two character tokens, if found return a T_TEXT token;
check for single character tokens, if found return a T_TEXT token;
all else fails return a single character T_CHAR token;
每次找到长度为L的令牌时,我们都会从
当前行并将列数增加L。我们保留了
当前行,以防需要显示错误。 请注意,如果我们已经
只需读取空格(请参阅第2步)即可增加列数
复杂:空格可能是制表符,一个制表符在视觉上代表了另一个
字符位置的数量,取决于制表位的大小。
标记器由解析器调用。 令牌生成器读取令牌后
尚未由解析器处理的令牌生成器将返回
一遍又一遍地使用相同的令牌,即解析器必须明确告知
令牌生成器,表示已处理了最后一个令牌,应将新令牌
从字符输入流中读取。
令牌类首先,让我们构建一个简单的Token类; 返回此类的实例
由令牌生成器; 这里是:
package compiler;
public class Token {
public static final Token ONE= new Token(1);
private double dbl; // filled if token is a number
private String str; // String representation of a token
private int typ; // the type of this token
// ctor for a double token:
public Token(double dbl) { this(dbl, ""+dbl, TokenTable.T_NUMB); }
// ctor for a non-double token:
public Token(String str, int type) { this(0.0, str, type); }
// general ctor:
public Token(double dbl, String str, int typ) {
this.dbl= dbl;
this.str= str;
this.typ= typ;
}
// mark this token as 'special':
public Token mark() {
str+= "@";
return this;
}
// getters:
public double getDbl() { return dbl; }
public String getStr() { return str; }
public int getTyp() { return typ; }
// token string and type as string:
public String toString() { return str+"["+typ+"]"; }
}
Token类只是一个简单的数据类:它存储的double值
由令牌化程序(如果有)读取,令牌的字符串表示形式
以及令牌的类型。 字符串值是该行的第一部分
由令牌生成器切掉。 Token类实现简单
访问器(“获取器”),它可以自行打印。 这也影响了一些
方便的构造函数。 这是一个简单干净的小类。 注意
第一个构造函数引用“ TokenTable”类。 那堂课
包含令牌生成器和小令牌类所需的静态数据。
令牌类具有一个可用的公共静态成员:常量ONE。
有一种方法需要更多说明,即“标记”方法。
解析器使用它来指示当前令牌有点“特殊”
并将其标记为令牌。 解析器将其用于重载的+和
-运算符,因为它们可以表示二进制加法或减法为
以及一元加减运算符。 稍后我们将回到棘手的问题
当我们深入研究此简单编译器的解析器部分时。
正则表达式资源稍后我们将讨论TokenTable(和其他表类)。 桌子
类包含该项目更有趣的部分所需的数据。
一个值得注意的细节是表类是使用外部变量填充的
属性文件。 这样可以轻松播放各个部分
(令牌生成器和解析器)并随意进行扩充。
属性文件之一是“ tokens.properties”文件,其中包含
分词器需要的正则表达式; 这里是:
space = ^\s*
number = ^\d+(\.\d*)?([eE][+-]?\d+)?
word = ^[A-Za-z_]\w*
symbol2 = ^(==|<=|>=|!=|^=|\+=|-=|\*=|/=|\+\+|--)
symbol1 = ^[=!<>+*/()^-]
char = ^\S
双\字符表示单个字符。
当一个属性
读取对象后,它将双反斜杠转换为单反斜杠。
API文档中详细说明了这些乱码
模式类的。 这是一个简短的概述:
第一行显示:^ \ s *,这意味着:在行的开头,我们希望为零
或更多 s字符。 s字符是Pattern类的特殊字符,
表示:任何空格字符(包括制表符)。
第二行比较复杂; 它显示为:一个数字是一个或多个数字
( d +部分)(可选)后接文字点和零个或多个数字
((.. d *)?部分。接下来可选的是小写或大写的“ e”或“ E”
读取(可选)后跟一个+或-号,后跟一个或多个数字
(([[eE] [+-]? d +)?部分。
第三行代表名称:任何字母(大写或小写)或
下划线后跟零或多个相同的数字(包括数字)。
第四行只是一个选择列表,其中每个选择都是我们的选择之一
两个字符标记。
第五行是单字符标记的列表,最后一行是^ S
表示:行首的任何非空格字符。
将这六行与上面列出顺序的段落进行比较
考虑哪些可能的令牌。
因为我们切断了该行的开头,所以剩下的
该行再次有新的起点。 常规字符中的'^'字符
表达式指定一行的开始,大大加快了匹配速度
一行文本的正则表达式。
本周文章的下一部分显示了最终的Tokenizer类。
到时候那里见。
亲切的问候,
乔斯
From: https://bytes.com/topic/java/insights/653378-compilers-2a-tokenizers
最后
以上就是敏感柠檬为你收集整理的编译器-2A:分词器的全部内容,希望文章能够帮你解决编译器-2A:分词器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复