编译器-2B:分词器

问候,

欢迎回到本周的第二部分。 本文部分显示

您是Tokenizer类。 让我们分小部分来做:


public class Tokenizer { 
    // default size of a tab character
    private static final int TAB= 8; 
    // the current line and its backup
    private String line;
    private String wholeLine; 
    // current column and tab size
    private int column;
    private int tab= TAB; 
    // the reader used for line reading
    private LineNumberReader lnr; 
    // last unprocessed token
    private Token token; 
这些是令牌生成器携带的私有成员变量; 最

他们不需要任何解释。 我们使用LineNumberReader

跟踪行号(原文如此)。 这是构造函数:


Tokenizer() { } 
public void initialize(Reader r) { 
    lnr= new LineNumberReader(r);
    line= null;
    token= null;
} 
他们都没什么可谈论的:只有一个构造函数,而没有

上市; 这意味着只有同一包中的其他类才能创建一个

分词器。 解析器位于同一包中; 解析器将

在使用该程序包实例化了Tokenizer之后,使用initialize方法

作用域默认构造函数。 initialize方法将Reader包裹在一个

LineNumberReader。 wapper阅读器用于实际读取

输入并更新行号。

这是解析器调用的两种方法:

 
public Token getToken() throws InterpreterException { 
    if (token == null) token= read();
    return token;
} 
public void skip() { token= null; } 
如果没有已经读取的令牌,我们将读取一个新的令牌,否则我们将简单地返回

与之前读取的令牌相同。 'skip()'方法向令牌生成器发出信号

当前令牌已被处理,因此第一种方法将读取新令牌

在下一次调用时再次令牌。

接下来是一些“获取器”:


public String getLine() { return wholeLine; } 
public int getLineNumber() { return lnr.getLineNumber(); } 
public int getColumn() { return column; } 
public int getTab() { return tab; } 
public void setTab(int tab) { if (tab > 0) this.tab= tab; } 
'setTab()'方法进行了一些健全性检查,而'getters'简单地

返回他们应该返回的东西。 让我们进入这个私密部分

在课堂上发生更多有趣的事情。 从令牌读取令牌后

行的“列”值需要更新。 'tab'变量包含

制表符大小的值,因此我们在更新时必须将其考虑在内

“列”值:


private void addColumn(String str) { 
    for (int i= 0, n= str.length(); i < n; i++)
        if (str.charAt(i) != '\t') column++;
        else column= ((column+tab)/tab)*tab;
} 
当字符不是制表符时,我们只需增加“列”值,

否则,我们将确定下一个Tabstop值。

以下方法检查是否需要读取下一行:

当前行为null或仅包含空格,我们需要阅读下一行。 如果

没什么要阅读的了,我们只是将行的备份设置为“ <eof>”

案例想显示刚刚阅读的内容。 只显示“ null”

看起来很傻。 此方法再次重置“列”值,因为

将在新行中扫描新令牌:

 
private String readLine() throws IOException { 
    for (; line == null || line.trim().length() == 0; ) {
        column= 0;
        if ((wholeLine= line= lnr.readLine()) == null) {
            wholeLine= "<eof>";
            return null;
        }
    } 
    return line;
} 
以下方法是Tokenizer的核心,即它尝试匹配

当前行与“匹配器”相对。 匹配器是模式的对应物

对象:匹配器尝试匹配时,模式会编译正则表达式

针对字符串的模式。 字符串是我们当前的“行”。 如果匹配

发现匹配的前缀从'line'中被切掉,并且'column'的值是

更新; 最后返回匹配的令牌(如果有)。 方法如下:


private String read(Matcher m) { 
    String str= null; 
    if (m.find()) {
        str= line.substring(0, m.end());
        line= line.substring(m.end()); 
        addColumn(str);
    } 
    return str;
} 
Tokenizer类的最后一个方法是最大的方法,但是有点

无聊的方法。 它所做的就是尝试使用不同的方式查找令牌

正则表达式,按照本周第一部分中介绍的顺序进行。

方法如下:


private Token read() throws InterpreterException {  
    String str; 
    try {
        if (readLine() == null)
            return new Token("eof", TokenTable.T_ENDT); 
        read(TokenTable.spcePattern.matcher(line)); 
        if ((str= read(TokenTable.numbPattern.matcher(line))) != null)
            return new Token(Double.parseDouble(str)); 
        if ((str= read(TokenTable.wordPattern.matcher(line))) != null)
            return new Token(str, TokenTable.T_NAME); 
        if ((str= read(TokenTable.sym2Pattern.matcher(line))) != null)
            return new Token(str, TokenTable.T_TEXT); 
        if ((str= read(TokenTable.sym1Pattern.matcher(line))) != null)
            return new Token(str, TokenTable.T_TEXT); 
        return new Token(read(TokenTable.charPattern.matcher(line)), TokenTable.T_CHAR);
    }
    catch (IOException ioe) {
        throw new TokenizerException(ioe.getMessage(), ioe);
    }
} 
正则表达式的模式由TokenTable类提供

而所有此方法所做的就是尝试以固定顺序匹配它们。 这就是全部

对我们的小语言进行词法分析是:

模式具有匹配项,则返回相应的令牌。 以前的方法

注意不要跳过和忘记未处理的令牌(只需将其返回)

一次又一次地直到令牌生成器被通知它实际上已经被

处理)。 其他方法(请参见上文)会在以下情况下读取下一行:

必要并随时更新列值。

您可能已经注意到TokenizerException抛出了

分词器代码。 TokenizerException是一个小类,它扩展了

InterpreterException类。 后一类更有趣,看起来

像这样:


public class InterpreterException extends Exception { 
    private static final long serialVersionUID = 99986468888466836L; 
    private String message; 
    public InterpreterException(String message) {  
        super(message); 
    } 
    public InterpreterException(String message, Throwable cause) {  
        super(message, cause); 
    } 
    public InterpreterException(Tokenizer tz, String message) {  
        super(message); 
        process(tz);
    } 
    public InterpreterException(Tokenizer tz, String message, Throwable cause) {  
        super(message, cause); 
        process(tz);
    } 
    private void process(Tokenizer tz) { 
        StringBuilder sb= new StringBuilder();
        String nl= System.getProperty("line.separator"); 
        sb.append("["+tz.getLineNumber()+":"+tz.getColumn()+"] "+
              super.getMessage()+nl);
        sb.append(tz.getLine()+nl);         
        for (int i= 1, n= tz.getColumn(); i < n; i++)
            sb.append('-');
        sb.append('^'); 
        message= sb.toString();
    } 
    public String getMessage() { 
        return (message != null)?message:super.getMessage();
    }
} 
看起来像大多数异常,即有一条消息和可能的“原因”

的例外(所谓的“根”例外)。 有一个有趣的

此InterpreterException中的方法:'process'方法。 当分词器

在此对象的构造时传递,它能够构造一个不错的对象

打印出错误消息; 错误消息看起来像他:


[line:column] error mesage
line that has a column with an error
---------------------^ 
StringBuilder用于连接三行的不同部分

信息; “ nl”变量包含系统的“行尾”序列

该应用程序正在运行(“ \ r”和“ \ n”的任何组合都是可能的)。

并连接正确数量的'-'符号以使'^'插入符号

显示在当前行下的正确位置。

Tokenizer使用的LineNumber阅读器对从0(零)开始的行进行计数,

人类喜欢从1(一个)开始计数,但是对我们来说很幸运,第一行(0)

已经被完全读取,并且LineNumberReader将返回下一行

我们在需要时为我们提供编号,因此无需调整值。

请注意,列号也从0开始,但是该行上至少有一个标记

已被读取,并且该列指向字符串中的位置

跟随令牌,因此这里也不需要调整。

当没有标记器提供给此构造函数时,仅提供

传递给超类将被返回,否则我们精心制作的

消息由getMessage()方法返回。

解析器在遇到InterpreterException时会广泛使用

令牌流中的语法错误。 他们将令牌生成器本身传递给新

InterpreterException,因此在任何可能的情况下,格式正确的错误消息都是

在打印出此InterpreterException消息时显示。

结束语

我在本周的文章中展示并解释了很多代码。

尝试理解代码,如果/如果您不这样做,请立即回复

了解一些。 编译器的构建是一项艰巨的任务,其中包含

许多棘手的细节。 本周展示了简单的令牌类; 长和

无聊的Tokenizer类和InterpreterException类。

在下一个技巧中,我将解释如何初始化表类。 那是什么时候

一遍又一遍,我将提供一些实际的代码作为附件,以便您

可以玩和尝试所有这一切。

本文的以下部分介绍了解析器,复杂的部分

我们的编译器。 解析器与简单的代码生成器紧密协作。

生成器生成代码(多么令人惊讶!),可以将其馈送到最后

class:解释器类本身。 生成的代码由指令组成;

它们有时很复杂,但大多数时候都是简单而连贯的作品

Intepreter激活的代码(Java指令序列)。

下周见

亲切的问候,

乔斯

From: https://bytes.com/topic/java/insights/739821-compilers-2b-tokenizers

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值