散歩に行く


This article is originally posted here.


あなたが囲碁に興味があるとき、あなたは何を参照しますか?書類?ソースコード?
ドキュメントを読むと、抽象的に理解できますが、APIが互いに関連しているかどうかはわかりません.
言うまでもなく、全体のソースコードを読んでください、そして、あなたはそれを完全に見ます、しかし、あなたは全部のものを読むことを試みることから疲れていなければなりません.
したがって、この記事はそれらの中間にあることを目指します.それを簡単にして、我々が通常書く試みコードが内部で表される方法を理解するために、ASTを通して散歩してみましょう.
ソースコードを解析する方法を詳しく調べ、ASTが構築された後に説明を開始します.
コードをASTに変換する方法について興味があるならDigging deeper into the analysis of Go-code .
始めましょう!

インターフェース


まず、ASTノードを表すインターフェースを簡単に説明します.
すべてのASTノードは ast.Node AST内の位置を返すインターフェイス.
追加では、実装する3つのメインインタフェースがありますast.Node :

  • ast.Expr — 式と型ノードの表現

  • ast.Stmt — 表明文ノード

  • ast.Decl — 宣言ノードの表現

  • すべてのノードが満足する定義から見ることができますast.Node インタフェース
    ast/ast.go
    // All node types implement the Node interface.
    type Node interface {
        Pos() token.Pos // position of first character belonging to the node
        End() token.Pos // position of first character immediately after the node
    }
    
    // All expression nodes implement the Expr interface.
    type Expr interface {
        Node
        exprNode()
    }
    
    // All statement nodes implement the Stmt interface.
    type Stmt interface {
        Node
        stmtNode()
    }
    
    // All declaration nodes implement the Decl interface.
    type Decl interface {
        Node
        declNode()
    }
    

    ウォーキングから始める


    歩き始めましょう!ASTに変換するファイルを見てください.
    package hello
    
    import "fmt"
    
    func greet() {
        fmt.Println("Hello, World")
    }
    
    何もファンシー-過度にシンプルなハロー、世界プログラム.この上に構築されたASTは次のとおりです.
    ASTファイル
    *ast.File {
    .  Package: dummy.go:1:1
    .  Name: *ast.Ident {
    .  .  NamePos: dummy.go:1:9
    .  .  Name: "hello"
    .  }
    .  Decls: []ast.Decl (len = 2) {
    .  .  0: *ast.GenDecl {
    .  .  .  TokPos: dummy.go:3:1
    .  .  .  Tok: import
    .  .  .  Lparen: -
    .  .  .  Specs: []ast.Spec (len = 1) {
    .  .  .  .  0: *ast.ImportSpec {
    .  .  .  .  .  Path: *ast.BasicLit {
    .  .  .  .  .  .  ValuePos: dummy.go:3:8
    .  .  .  .  .  .  Kind: STRING
    .  .  .  .  .  .  Value: "\"fmt\""
    .  .  .  .  .  }
    .  .  .  .  .  EndPos: -
    .  .  .  .  }
    .  .  .  }
    .  .  .  Rparen: -
    .  .  }
    .  .  1: *ast.FuncDecl {
    .  .  .  Name: *ast.Ident {
    .  .  .  .  NamePos: dummy.go:5:6
    .  .  .  .  Name: "greet"
    .  .  .  .  Obj: *ast.Object {
    .  .  .  .  .  Kind: func
    .  .  .  .  .  Name: "greet"
    .  .  .  .  .  Decl: *(obj @ 23)
    .  .  .  .  }
    .  .  .  }
    .  .  .  Type: *ast.FuncType {
    .  .  .  .  Func: dummy.go:5:1
    .  .  .  .  Params: *ast.FieldList {
    .  .  .  .  .  Opening: dummy.go:5:11
    .  .  .  .  .  Closing: dummy.go:5:12
    .  .  .  .  }
    .  .  .  }
    .  .  .  Body: *ast.BlockStmt {
    .  .  .  .  Lbrace: dummy.go:5:14
    .  .  .  .  List: []ast.Stmt (len = 1) {
    .  .  .  .  .  0: *ast.ExprStmt {
    .  .  .  .  .  .  X: *ast.CallExpr {
    .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
    .  .  .  .  .  .  .  .  X: *ast.Ident {
    .  .  .  .  .  .  .  .  .  NamePos: dummy.go:6:2
    .  .  .  .  .  .  .  .  .  Name: "fmt"
    .  .  .  .  .  .  .  .  }
    .  .  .  .  .  .  .  .  Sel: *ast.Ident {
    .  .  .  .  .  .  .  .  .  NamePos: dummy.go:6:6
    .  .  .  .  .  .  .  .  .  Name: "Println"
    .  .  .  .  .  .  .  .  }
    .  .  .  .  .  .  .  }
    .  .  .  .  .  .  .  Lparen: dummy.go:6:13
    .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {
    .  .  .  .  .  .  .  .  0: *ast.BasicLit {
    .  .  .  .  .  .  .  .  .  ValuePos: dummy.go:6:14
    .  .  .  .  .  .  .  .  .  Kind: STRING
    .  .  .  .  .  .  .  .  .  Value: "\"Hello, World\""
    .  .  .  .  .  .  .  .  }
    .  .  .  .  .  .  .  }
    .  .  .  .  .  .  .  Ellipsis: -
    .  .  .  .  .  .  .  Rparen: dummy.go:6:28
    .  .  .  .  .  .  }
    .  .  .  .  .  }
    .  .  .  .  }
    .  .  .  .  Rbrace: dummy.go:7:1
    .  .  .  }
    .  .  }
    .  }
    .  Scope: *ast.Scope {
    .  .  Objects: map[string]*ast.Object (len = 1) {
    .  .  .  "greet": *(obj @ 27)
    .  .  }
    .  }
    .  Imports: []*ast.ImportSpec (len = 1) {
    .  .  0: *(obj @ 12)
    .  }
    .  Unresolved: []*ast.Ident (len = 1) {
    .  .  0: *(obj @ 46)
    .  }
    }
    

    ハウツー歩き方


    我々がしなければならないすべては、このASTノードを深さの最初の順序で横断することです.
    呼び出しによって各ノードを一つずつ印刷しましょう ast.Inspect() 再帰的に.
    また、直接ASTを印刷して、一般的に読みにくいものを見ます.
    それを防ぐために、我々は使用するつもりです ast.Print , ASTの人間読書のための強力なAPI
    package main
    
    import (
        "fmt"
        "go/ast"
        "go/parser"
        "go/token"
    )
    
    func main() {
        fset := token.NewFileSet()
        f, _ := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments)
    
        ast.Inspect(f, func(n ast.Node) bool {
            // Called recursively.
            ast.Print(fset, n)
            return true
        })
    }
    
    var src = `package hello
    
    import "fmt"
    
    func greet() {
        fmt.Println("Hello, World")
    }
    `
    

    ASTファイル


    訪問する最初のノードは *ast.File , これはすべてのASTノードのルートです.
    それはast.Node インターフェイス.
    ast.File パッケージ名、インポート宣言、および子ノードとしての関数宣言を参照します.
    正確に言えばComments といっても、シンプルのために省略しましょう.
    パッケージ名から始めましょう.
    ( NIL値のフィールドは省略されます.the document 各ノード型のフィールドの完全なリストについては

    パッケージ名


    ASTident


    *ast.Ident {
    .  NamePos: dummy.go:1:9
    .  Name: "hello"
    }
    
    パッケージ名はASTノードタイプ *ast.Ident , を実装するast.Expr インターフェイス.
    すべての識別子はこの構造で表されます.これは主にファイル名内の名前とソース位置が含まれます.
    上のコードから、パッケージ名がhello の値はdummy.go .
    我々はこのノードに深く潜ることはできません*ast.File .

    インポート宣言


    ASTgendecl


    *ast.GenDecl {
    .  TokPos: dummy.go:3:1
    .  Tok: import
    .  Lparen: -
    .  Specs: []ast.Spec (len = 1) {
    .  .  0: *ast.ImportSpec {/* Omission */}
    .  }
    .  Rparen: -
    }
    
    インポート宣言はASTノードタイプで表されます *ast.GenDecl , を実装するast.Decl インターフェイス.ast.GenDecl 関数以外のすべての宣言を表しますつまり、import、const、var、typeです.Tok 字句トークンを表します.これは、宣言の内容を指定します(importまたはconstまたはtypeまたはvar).
    このASTノードは、インポート宣言が2007年に3行目にあることを伝えますdummy.go .
    会いましょうast.GenDecl 深さ優先順位.見る*ast.ImportSpec , 次のノード.

    AST輸入


    *ast.ImportSpec {
    .  Path: *ast.BasicLit {/* Omission */}
    .  EndPos: -
    }
    
    ast.ImportSpec ノードは単一のインポート宣言に対応します.
    これは ast.Spec インターフェイス.
    訪問Path インポートパスについてより理解することができます.さあ行きましょう.

    ASTバスライト


    *ast.BasicLit {
    .  ValuePos: dummy.go:3:8
    .  Kind: STRING
    .  Value: "\"fmt\""
    }
    
    ast.BasicLit ノードは基本型のリテラルを表します.
    これはast.Expr インターフェイス.
    これはトークンの種類とtoken.INT , token.FLOAT , token.IMAG , token.CHAR , or token.STRING を使用できます.
    からast.ImportSpec and ast.BasicLit , 「FMT」と呼ばれるパッケージを輸入しているのがわかります.
    我々は、より深くダイビングすることはできません、再びトップレベルに戻りましょう.

    関数宣言


    ASTファンクション


    *ast.FuncDecl {
    .  Name: *ast.Ident {/* Omission */}
    .  Type: *ast.FuncType {/* Omission */}
    .  Body: *ast.BlockStmt {/* Omission */}
    }
    
    ast.FuncDecl ノードは関数宣言を表します.
    それはast.Node インターフェイス.
    それらを順に見てみましょうName , 関数名を表す.

    ASTident


    *ast.Ident {
    .  NamePos: dummy.go:5:6
    .  Name: "greet"
    .  Obj: *ast.Object {
    .  .  Kind: func
    .  .  Name: "greet"
    .  .  Decl: *(obj @ 0)
    .  }
    }
    
    これが2回目になったので、基本的な説明を省略しましょう.
    注目すべきは *ast.Object .
    識別子が参照するオブジェクトを表しますが、なぜこれが必要ですか?
    ご存知のように、Goはスコープの概念を持ちます.これは、識別子が指定された定数、型、変数、関数、ラベル、またはパッケージを表すソーステキストの範囲です.
    The Decl フィールドは識別子の範囲を識別するために識別子が宣言された場所を示します.
    同一のオブジェクトを指す識別子は、同じものを共有する*ast.Object .

    AST機能型


    *ast.FuncType {
    .  Func: dummy.go:5:1
    .  Params: *ast.FieldList {/* Omission */}
    }
    
    戻って親の世代は、古い世代になる ast.FuncType 「func」キーワードのパラメータ、結果、位置を含む関数シグネチャを含んでいます.

    ASTフィールドリスト


    *ast.FieldList {
    .  Opening: dummy.go:5:11
    .  List: nil
    .  Closing: dummy.go:5:12
    }
    
    ast.FieldList ノードは、括弧または括弧で囲まれたフィールドのリストを表します.
    関数パラメータは、定義されている場合に表示されますが、今回はNoneです.List フィールドは *ast.Field これは、識別子と型のペアを含みます.
    これは非常に汎用性であり、様々なノードに使用されます *ast.StructType , *ast.InterfaceType , そしてここ.
    つまり、次のように識別子を識別子にマッピングするときに必要です.
    foo int
    bar string
    
    ループバックしましょう*ast.FuncDecl 再び、ビットに飛び込むBody , 最後のフィールド.

    ASTブロックSTMT


    *ast.BlockStmt {
    .  Lbrace: dummy.go:5:14
    .  List: []ast.Stmt (len = 1) {
    .  .  0: *ast.ExprStmt {/* Omission */}
    .  }
    .  Rbrace: dummy.go:7:1
    }
    
    ast.BlockStmt ノードはブレース付きのリストを表します.
    これはast.Stmt インターフェイス.
    これは、ステートメントのリストがあります.どのような想像できるノード!

    AST東北大理


    *ast.ExprStmt {
    .  X: *ast.CallExpr {/* Omission */}
    }
    
    ast.ExprStmt ステートメント一覧の式を表します.
    これはast.Stmt インターフェースとシングルast.Expr .

    ASTcallexpr


    *ast.CallExpr {
    .  Fun: *ast.SelectorExpr {/* Omission */}
    .  Lparen: dummy.go:6:13
    .  Args: []ast.Expr (len = 1) {
    .  .  0: *ast.BasicLit {/* Omission */}
    .  }
    .  Ellipsis: -
    .  Rparen: dummy.go:6:28
    }
    
    ast.CallExpr 関数を呼び出す式を表します.
    見るフィールドはFun , 呼び出す関数Args , 渡す引数のリスト.

    ASTselectorexpr


    *ast.SelectorExpr {
    .  X: *ast.Ident {
    .  .  NamePos: dummy.go:6:2
    .  .  Name: "fmt"
    .  }
    .  Sel: *ast.Ident {
    .  .  NamePos: dummy.go:6:6
    .  .  Name: "Println"
    .  }
    }
    
    ast.SelectorExpr 式に続いてセレクタを表します.
    簡単に言えばfmt.Println .

    ASTバスライト


    *ast.BasicLit {
    .  ValuePos: dummy.go:6:14
    .  Kind: STRING
    .  Value: "\"Hello, World\""
    }
    
    もはや説明、こんにちは、世界が必要!

    ボトムライン


    私は、私が導入したノードタイプでいくつかのフィールドを除外しました、そして、まだ多くの他のノードタイプがあります.
    それにもかかわらず、私は、それが少しラフであっても、実際に散歩を歩くことが重要であると言います.そして何よりも、それはかなりの楽しみです.
    コピーして貼り付けのコードは、“歩いてどのように”セクションで示され、あなたのPC上を歩くしようとしている.