ゼロからLua解釈器のToken実現を実現

5323 ワード

警告⚠️:これは臭くて長い一連のチュートリアルになります.チュートリアルが終わると、性能が悪く、拡張性が悪く、標準ライブラリが完備していないほか、公式とほとんど変わらないLua言語解釈器があります.はっきり言って、このシリーズのチュートリアルはおもちゃの言語を実現して、勉強だけで、実用性がありません.注意してくださいFollow、注意してくださいFollow、注意してくださいFollow.
これはこのシリーズのチュートリアルの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のアカウントをフォローしてくれればもっとよかったのに、私もこのシリーズを書くモチベーションがもっとあります.:)