在上一節中我們提到了,我們可以根據一種叫 有限自動機 的東西將字符串分割成多個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
/// 否則狀態機遷移至Identifier
if 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運行時環境。
配置好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
編譯完成得到如下文件
然后使用
javac *.java
編譯完成之后,通過運行grun命令來解析并輸出對應的Token(s)
grun FlexDSLLexer tokens -tokens Hello.play
其中Hello.play內容如下
int name = "人\\n字";
true
false
最后得到如下輸出,不僅成功的解析出來了我們指定的Token,還把對應的行列都輸出了。這樣當我們在解析出錯時也可以報具體的錯誤信息了。到此,我們的詞法解析就完成了,接下來我們將進行語法分析。
-
SWIFT
+關注
關注
0文章
116瀏覽量
23802 -
原理
+關注
關注
4文章
550瀏覽量
44898 -
代碼
+關注
關注
30文章
4788瀏覽量
68612
發布評論請先 登錄
相關推薦
評論