ゼロからLua解釈器のToken実現を実現
5323 ワード
警告⚠️:これは臭くて長い一連のチュートリアルになります.チュートリアルが終わると、性能が悪く、拡張性が悪く、標準ライブラリが完備していないほか、公式とほとんど変わらないLua言語解釈器があります.はっきり言って、このシリーズのチュートリアルはおもちゃの言語を実現して、勉強だけで、実用性がありません.注意してくださいFollow、注意してくださいFollow、注意してくださいFollow.
これはこのシリーズのチュートリアルの2編目です.前の文章を見たことがない場合は、最初から見てください.
前言
本節ではこのチュートリアルを正式に開始し,本稿ではSLuaの文法解析モジュールの重要なタイプであるTokenの実現に着手する.
Token定義
Tokenとは,プログラムの最も基本的な要素を表す語法単位である.たとえば、プログラミング言語の各キーワード、オペレータ、Identifier、数字、文字列などは、Tokenです.最初のステップでは、Tokenを表す構造体を定義する必要があります.
まず、Tokenにはタイプを表すフィールドが必要です.通常、列挙を使用してこのタスクを完了します.しかし、Go言語は列挙のサポートを提供していないので、const式を使用して一連の定数を定義してこの目的を達成します.
通常のintではなくstringタイプを使用する理由は、後で説明されます.
前回の記事で紹介したように、タスクを簡略化するために、私たちが初めて実現したのは深刻な去勢されたLuaなので、それに応じてTokenのタイプも少なくなった.
タイプの定義があれば、Token構造体を定義することができます.
このうちCategoryに格納されているのがTokenのタイプです.キーワードとオペレータの場合、このような定義は十分です.他の追加情報は必要ありません.しかし、TokenID、TokenStringには、特定の値を格納する追加の文字列が必要であり、TokenNumberの場合、特定の数値を格納するためにfloat 64のフィールドが必要である.
ストレージスペースを無駄にしないために、C言語ではunion構造を使用してこの目的を達成することを容易に考えます.
しかし残念なことに、Go言語はunionをサポートしていないので、別の道を切り開くしかありません.幸いなことにGo言語は強力なタイプを提供しています:interface{}.これは汎用タイプで、任意のタイプの値を格納できます.stringおよびfloat 64タイプの値を格納するために使用できます.したがって、Tokenタイプは次のように定義されています.
実装を簡略化するために、Luaでは、整数値においても浮動小数点数においても、最下位では1つのfloat 64に格納される.
また,ユーザの体験のためにプログラムエラーが発生した場合,どの行のどれが間違っているかをユーザに伝える必要があるため,Tokenが存在する行と列を記録する必要があり,現在のToken定義は以下の通りである.
この構造体があれば、いくつかの方法を定義する必要があります.
上記のNewTokenメソッドは、ファイルの最後、すなわちソースコードの読み取りが終了したことを示すEnd Of Fileを意味するTokenEOFタイプのTokenを返します.
デフォルトとしてTokenEOFを使用します.
印刷をデバッグするために、Tokenにfmtを満たすString方法を定義した.Stringerインタフェース:
Stringメソッドでは、TokenのタイプがTokenNumber、TokenID、またはTokenStringの場合、値はValueフィールドに格納されている特定の値に戻ります.そうでなければ、Tokenのタイプを直接返します.これも私たちのタイプ列挙が一般的なintタイプではなくstringタイプを使用している理由です.
1.Go言語はJavaなどの言語とは異なり、インタフェースの実現メカニズムは暗黙的である.インタフェースの要求を実現する方法は、インタフェースを実現することに相当し、明示的に宣言する必要はありません.だからもちろんキーワードimplementsもありません.
2.fmt.Stringerの定義は次のとおりです.
さらに、Token値を深度コピーするためのCloneメソッドも定義しました.
さらに,ある文字列がLuaのキーワードの1つであるかどうかを決定するためのヘルプ関数も提供した.この関数は、次の構文解析で使用されます.
Go言語のswitch文はbreakを使用する必要はありません.
これでTokenタイプの完全な定義が完了します.完全なソースコード:アドレスを表示できます.
ソースコードの取得
コードはGithubに管理されています:SLua、各段階のコードは私はreleaseを作成して、あなたは直接ダウンロードして参照することができます.ソースコードは提供されていますが、直接コピーして貼り付けることはお勧めしません.このように学んだ知識は忘れやすいからです.
Githubと遊び始めたばかりなので、何のファンも注目もありません(泣)、このチュートリアルが役に立つと思ったら、文章に好きなものを注文したり、Githubのプロジェクトにスターを注文したりしないでください.FollowとGithubのアカウントをフォローしてくれればもっとよかったのに、私もこのシリーズを書くモチベーションがもっとあります.:)
これはこのシリーズのチュートリアルの2編目です.前の文章を見たことがない場合は、最初から見てください.
前言
本節ではこのチュートリアルを正式に開始し,本稿ではSLuaの文法解析モジュールの重要なタイプであるTokenの実現に着手する.
Token定義
Tokenとは,プログラムの最も基本的な要素を表す語法単位である.たとえば、プログラミング言語の各キーワード、オペレータ、Identifier、数字、文字列などは、Tokenです.最初のステップでは、Tokenを表す構造体を定義する必要があります.
まず、Tokenにはタイプを表すフィールドが必要です.通常、列挙を使用してこのタスクを完了します.しかし、Go言語は列挙のサポートを提供していないので、const式を使用して一連の定数を定義してこの目的を達成します.
const (
TokenAnd string = "and"
TokenDo = "do"
TokenElse = "else"
TokenElseif = "elseif"
TokenEnd = "end"
TokenFalse = "false"
TokenIf = "if"
TokenLocal = "local"
TokenNil = "nil"
TokenNot = "not"
TokenOr = "or"
TokenThen = "then"
TokenTrue = "true"
TokenWhile = "while"
TokenID = ""
TokenString = ""
TokenNumber = ""
TokenAdd = "+"
TokenSub = "-"
TokenMul = "*"
TokenDiv = "/"
TokenLen = "#"
TokenLeftParen = "("
TokenRightParen = ")"
TokenAssign = "="
TokenSemicolon = ";"
TokenComma = ","
TokenEqual = "=="
TokenNotEqual = "~="
TokenLess = ""
TokenGreaterEqual = ">="
TokenConcat = ".."
TokenEOF = ""
)
通常のintではなくstringタイプを使用する理由は、後で説明されます.
前回の記事で紹介したように、タスクを簡略化するために、私たちが初めて実現したのは深刻な去勢されたLuaなので、それに応じてTokenのタイプも少なくなった.
タイプの定義があれば、Token構造体を定義することができます.
type Token struct {
Category string
}
このうちCategoryに格納されているのがTokenのタイプです.キーワードとオペレータの場合、このような定義は十分です.他の追加情報は必要ありません.しかし、TokenID、TokenStringには、特定の値を格納する追加の文字列が必要であり、TokenNumberの場合、特定の数値を格納するためにfloat 64のフィールドが必要である.
ストレージスペースを無駄にしないために、C言語ではunion構造を使用してこの目的を達成することを容易に考えます.
union {
double num;
char* str;
};
しかし残念なことに、Go言語はunionをサポートしていないので、別の道を切り開くしかありません.幸いなことにGo言語は強力なタイプを提供しています:interface{}.これは汎用タイプで、任意のタイプの値を格納できます.stringおよびfloat 64タイプの値を格納するために使用できます.したがって、Tokenタイプは次のように定義されています.
type Token struct {
Value interface{}
Category string
}
実装を簡略化するために、Luaでは、整数値においても浮動小数点数においても、最下位では1つのfloat 64に格納される.
また,ユーザの体験のためにプログラムエラーが発生した場合,どの行のどれが間違っているかをユーザに伝える必要があるため,Tokenが存在する行と列を記録する必要があり,現在のToken定義は以下の通りである.
type Token struct {
Value interface{}
Line int
Column int
Category string
}
この構造体があれば、いくつかの方法を定義する必要があります.
func NewToken() *Token {
token := new(Token)
token.Category = TokenEOF
return token
}
上記のNewTokenメソッドは、ファイルの最後、すなわちソースコードの読み取りが終了したことを示すEnd Of Fileを意味するTokenEOFタイプのTokenを返します.
デフォルトとしてTokenEOFを使用します.
印刷をデバッグするために、Tokenにfmtを満たすString方法を定義した.Stringerインタフェース:
func (t *Token) String() string {
var s string
if t.Category == TokenNumber || t.Category == TokenID ||
t.Category == TokenString {
s = fmt.Sprintf("%v", t.Value)
} else {
s = t.Category
}
return s
}
Stringメソッドでは、TokenのタイプがTokenNumber、TokenID、またはTokenStringの場合、値はValueフィールドに格納されている特定の値に戻ります.そうでなければ、Tokenのタイプを直接返します.これも私たちのタイプ列挙が一般的なintタイプではなくstringタイプを使用している理由です.
1.Go言語はJavaなどの言語とは異なり、インタフェースの実現メカニズムは暗黙的である.インタフェースの要求を実現する方法は、インタフェースを実現することに相当し、明示的に宣言する必要はありません.だからもちろんキーワードimplementsもありません.
2.fmt.Stringerの定義は次のとおりです.
type Stringer interface {
String() string
}
さらに、Token値を深度コピーするためのCloneメソッドも定義しました.
func (t *Token) Clone() *Token {
return &Token{
Value: t.Value,
Line: t.Line,
Column: t.Column,
Category: t.Category,
}
}
さらに,ある文字列がLuaのキーワードの1つであるかどうかを決定するためのヘルプ関数も提供した.この関数は、次の構文解析で使用されます.
func isKeyword(id string) bool {
switch id {
case TokenAnd, TokenDo, TokenElse, TokenElseif, TokenEnd,
TokenFalse, TokenIf, TokenLocal, TokenNil, TokenNot,
TokenOr, TokenThen, TokenTrue, TokenWhile:
return true
default:
return false
}
}
Go言語のswitch文はbreakを使用する必要はありません.
これでTokenタイプの完全な定義が完了します.完全なソースコード:アドレスを表示できます.
ソースコードの取得
コードはGithubに管理されています:SLua、各段階のコードは私はreleaseを作成して、あなたは直接ダウンロードして参照することができます.ソースコードは提供されていますが、直接コピーして貼り付けることはお勧めしません.このように学んだ知識は忘れやすいからです.
Githubと遊び始めたばかりなので、何のファンも注目もありません(泣)、このチュートリアルが役に立つと思ったら、文章に好きなものを注文したり、Githubのプロジェクトにスターを注文したりしないでください.FollowとGithubのアカウントをフォローしてくれればもっとよかったのに、私もこのシリーズを書くモチベーションがもっとあります.:)