用c++自制词法分析器_(二) 词法分析Antlr

在上一节中我们提到了,我们可以根据一种叫 有限自动机 的东西将字符串分割成多个Token。

下面是一段swift代码,其中实现了一些基础Token的解析,原理就是: 有限自动机

//
//  ScriptLexer.swift
//  MyScriptCompiler
//
//  Created by legendry on 2019/8/21.
//  Copyright © 2019 legendry. All rights reserved.
//
import Foundation
/// 脚本语言词法分析器
public class ScriptLexer {
    
    public enum ScriptLexer: Error {
        /// 源码为空
        case SourceCodeEmpty
        /// 语法错误
        case SyntaxError(reason: String)
    }
    /// 词法分析器的有限状态机状态
    var fsmType = FSMType.Initial
    /// 默认token为nil
    var token: ScriptToken? = nil
    var _tmpTokens = [ScriptToken]()
    /// 初始化新的token
    /// 将状态机迁移到新的状态
    private func initToken(_ c: UInt8) throws -> FSMType {
        var _fsmType = FSMType.Unknow
        if let _t = token {
            /// 将解析到的token保存起来
            _tmpTokens.append(_t)
            token = nil
        }
        if c.isLetter {
            /// 当我们解析到字符i时,当前认为他是一个标识符
            token = ScriptToken(type: .Identifier)
            token?.appendTokenText(c: c)
            if c.char == "i" {
                /// 状态机状态切换至标识符i,如果后续是nt并以空格结束,则解析成int
                /// 否则解析成标识符
                _fsmType = .Identifier_Int_i
            } else {
                _fsmType = .Identifier
            }
        } else if c.char == "=" {
            token = ScriptToken(type: .EQ)
            token?.appendTokenText(c: c)
            _fsmType = .EQ
        } else if c.isDigit {
            token = ScriptToken(type: .IntLiteral)
            token?.appendTokenText(c: c)
            _fsmType = .IntLiteral
        } else if c.char == ">" {
            token = ScriptToken(type: .GT)
            token?.appendTokenText(c: c)
            _fsmType = .GT
        } else if c.char == " {
            token = ScriptToken(type: .LT)
            token?.appendTokenText(c: c)
            _fsmType = .LT
        } else if c.char == "-" {
            token = ScriptToken(type: .Minus)
            token?.appendTokenText(c: c)
            _fsmType = .Minus
        } else if c.char == "*" {
            token = ScriptToken(type: .Star)
            token?.appendTokenText(c: c)
            _fsmType = .Star
        } else if c.char == "+" {
            token = ScriptToken(type: .Plus)
            token?.appendTokenText(c: c)
            _fsmType = .Plus
        } else if c.char == "/" {
            token = ScriptToken(type: .Slash)
            token?.appendTokenText(c: c)
            _fsmType = .Slash
        } else if c.char == "(" {
            token = ScriptToken(type: .LeftBracket)
            token?.appendTokenText(c: c)
            _fsmType = .LeftBracket
        } else if c.char == ")" {
            token = ScriptToken(type: .RightBracket)
            token?.appendTokenText(c: c)
            _fsmType = .RightBracket
        } else {if c.isValid {
                _fsmType = .Initial
            } else {
                throw ScriptLexer.SyntaxError(reason: "不支持: \(c.char)")
            }
        }return _fsmType
    }
    /// 解析脚本,生成Token
    /// 利用有限状态自动机在不同状态之间迁移得到不同的Token
    /// int age = 45
    /// age >= 3
    public func analysis(script: String) throws -> ScriptTokenReader {
        _tmpTokens.removeAll()
        fsmType = FSMType.Initial
        token = nil
        guard script.count > 0 else {
            throw ScriptLexer.SourceCodeEmpty
        }let charReader = CharReader(script)
        /// 开始分析源码while let c = charReader.read() {
            switch fsmType {case .Initial:
                self.fsmType = try initToken(c)case .Identifier_Int_i:
                /// 第一个字母是i,如果第二个字母是n则状态机迁移至Identifier_Int_n
                /// 否则状态机迁移至Identifierif c.char == "n" {
                    self.fsmType = .Identifier_Int_n
                    token?.appendTokenText(c: c)
                } else if c.isTail {
                    /// 当前token标识完成
                    /// 状态机重置
                    self.fsmType = try initToken(c)
                } else {
                    /// 状态机迁移至标识符状态,继续解析标识符token
                    self.fsmType = .Identifier
                    token?.appendTokenText(c: c)
                }case .Identifier_Int_n:if c.char == "t" {
                    self.fsmType = .IntLiteral
                    token?.appendTokenText(c: c)
                    /// 这里暂时将类型切换成Int,如果后面还有字符则表示是一个标识符,再切换类型
                    token?.type = .Int
                } else if c.isTail {
                    /// 当前token标识完成
                    /// 状态机重置
                    self.fsmType = try initToken(c)
                } else {
                    /// 状态机迁移至标识符状态,继续解析标识符token
                    self.fsmType = .Identifier
                    token?.appendTokenText(c: c)
                }case .IntLiteral:if c.isTail {
                    /// 当前token标识完成
                    /// 状态机重置
                    self.fsmType = try initToken(c)
                } else if c.char == "+" || c.char == "-" || c.char == "*" || c.char == "/" /*|| c.char == "(" || c.char == ")"*/ {
                    self.fsmType = try initToken(c)
                } else if c.isLetter {
                    throw ScriptLexer.SyntaxError(reason: "非数字字面量")
                } else {
                    token?.appendTokenText(c: c)
                }case .Identifier:if c.isTail {
                    self.fsmType = try initToken(c)
                } else {
                    token?.appendTokenText(c: c)
                }case .EQ:if c.isTail {
                    token?.type = .Assignment
                    self.fsmType = try initToken(c)
                } else {
                    throw ScriptLexer.SyntaxError(reason: "语法异常")
                }case .GE, .LE:if c.isTail {
                    self.fsmType = try initToken(c)
                } else {
                    throw ScriptLexer.SyntaxError(reason: "语法异常")
                }case .LeftBracket, .RightBracket:
                self.fsmType = try initToken(c)case .GT:if c.isTail {
                    self.fsmType = try initToken(c)
                } else if c.char == "=" {
                    self.fsmType = .GE
                    token?.type = .GE
                    token?.appendTokenText(c: c)
                } else {
                    throw ScriptLexer.SyntaxError(reason: "语法异常")
                }case .LT:if c.isTail {
                    self.fsmType = try initToken(c)
                } else if c.char == "=" {
                    self.fsmType = .LE
                    token?.type = .LE
                    token?.appendTokenText(c: c)
                } else {
                    throw ScriptLexer.SyntaxError(reason: "语法异常")
                }case .Minus, .Plus, .Star, .Slash:if c.isTail || c.isDigit {
                    self.fsmType = try initToken(c)
                } else {
                    throw ScriptLexer.SyntaxError(reason: "语法异常")
                }
            default: break
            }
        }if let _t = token {
            /// 将解析到的token保存起来
            _tmpTokens.append(_t)
            token = nil
        }let tokenReader = ScriptTokenReader.init(_tmpTokens)return tokenReader
    }
}

如果要快速落地一个DSL原型Demo,全部都自己去写似乎有点慢。所以我们需要借助于工具来帮我们解析Token, 这个工具叫做: Antlr https://www.antlr.org/

这个工具支持很多语言,C++,Swift,Java....,常用的编码语言都可以。你可以选择一个合适你的语言来实现DSL了。

下载并配置好Antlr,Antlr本身是Java实现的,所以你的环境要运行Antlr需要有Java运行时环境。

6dfc0ccb0f3b33617ca06bdbb82495d5.png

配置好Antlr之后,我们就可以借助Antlr来实现我们的词法分析了。Antlr通过解析规则文件来分析我们要分割的Token的规则,规则则是用正则表达示来书写。

lexer grammar FlexDSLLexer;
//关键字
If: 'if';
FOR: 'for';
WHILE: 'while';
IN: 'in';
/// 基础数据类型
Int: 'int';
Double: 'double';
Float: 'float';
True: 'true';
False: 'false';
//字面量
IntLiteral: [0-9]+;
DoubleLiteral: [0-9] . [0-9]*;
StringLiteral: '"' .*? '"'; //字符串字面量
//操作符
AssignmentOP: '=';
RelationalOP: '>' | '>=' | ' | '<=';
Star: '*';
Plus: '+';
Sharp: '#';
SemiColon: ';';
Dot: '.';
Comm: ',';
LeftBracket: '[';
RightBracket: ']';
LeftBrace: '{';
RightBrace: '}';
LeftParen: '(';
RightParen: ')';
//标识符
Id: [a-zA-Z_] ([a-zA-Z_] | [0-9])*;
//空白字符,抛弃
Whitespace: [ \t]+ -> skip;
Newline: ( '\r' '\n'? | '\n') -> skip;

以上的规则文件内容指定了我们要从字符串中解析出来的Token,每一个Token都有一个名字,后面对应的则是这个Token的规则。把这个文件保存到FlexDSLLexer.g4

然后通过命令来编译

antlr4 FlexDSLLexer.g4

编译完成得到如下文件

4285ef95be0d54751ef575bd2473b801.png然后使用

javac *.java

编译完成之后,通过运行grun命令来解析并输出对应的Token(s)

grun FlexDSLLexer tokens -tokens Hello.play 

其中Hello.play内容如下

int name = "人\n字";
true
false

最后得到如下输出,不仅成功的解析出来了我们指定的Token,还把对应的行列都输出了。这样当我们在解析出错时也可以报具体的错误信息了。482a3a33b357c1d8b70661952272b65d.png到此,我们的词法解析就完成了,接下来我们将进行语法分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值