ソースコードの定義へジャンプ



導入
こんにちは!久しぶりにクールな機能をしてきました.最近仕事をしてきたんですけどね.
この機能は定義にジャンプします(私はポストの残りの部分でJTDを省略します)、そしてソースコードをよりナビゲート可能にすることができます.

TLドクター
この機能の例を見るには、次のリンクを参照できます.
  • Prelude.JSON.renderAs https://hydra.dhall-lang.org/build/70743/download/1/docs/JSON/renderAs.dhall.html
  • すべてのファイルをJumpTo このリストのプレフィックスhttps://hydra.dhall-lang.org/build/70746/download/1/docs/

  • 定義へジャンプ
    あなたのプロジェクト(どんなプログラミング言語でも)のためにドキュメンテーションジェネレータを使用する主な目的は、あなたのユーザーがあなたのライブラリ/フレームワーク/パッケージ/何を使うかを学ぶことを簡単にすることです.ソースコードコメントの解析などの簡単なことdhall-docs すでに十分ですが、我々は常にコードを発見しやすく読みやすくする機能を追加することができます.
    定義へのジャンプは、ほとんどのドキュメントジェネレータツールが持っている機能です.使いましょうhaddock 例としてhaddock Haskellパッケージのドキュメントを生成します.
  • ソースコードは、ユーザーがドキュメントとしてマークしたことをコメントします.
  • パッケージの実際のソースコード.
  • 我々が使うならばdhall 例えばパッケージです.
  • これは生成されたドキュメントですDhall.Core 再エクスポートDhall.Syntax ) https://hackage.haskell.org/package/dhall-1.34.0/docs/Dhall-Core.html ;
  • これがhaddock ソースコードをレンダリングしますDhall.Syntax モジュールhttps://hackage.haskell.org/package/dhall-1.34.0/docs/src/Dhall.Syntax.html
  • レンダリングされたソースコードを開く場合Dhall.Syntax , 次のようになります.
  • これは、構文強調表示を使用してレンダリングされます
  • これはデータ型と関数名のリンクでいっぱいです.これは、クリック可能であり、モジュール内の何かの実装を共有することができます.例えば、あなたはここでの定義を見ることができます Expr データタイプ:dhallのAST.
  • 私たちは同じに追加しますdhall-docs : ファイルを取ると、各名前が定義されている場所を参照するには静的な分析を行うと、ユーザーがそれをホバリングし、ユーザーがクリックしたときにその名前の定義にジャンプするときに正しくそれを強調表示します.

    実装

    ダムオールスターズ
    The Expr からのデータ型dhall パッケージはパースされた式のソーススパンを取得するのに便利なコンストラクタです.
    data Expr s a =
        ...
        | Note s (Expr s a)
        ...
    
    あなたはそのハドックを見ることができますhere )
    …の目的Note 問題のある表現を強調することで有用な構文解析と型エラーを作成することです.dhallパーサが出力するExpr Src Import 解説Src and Import が見つかるExpr haddock ) それぞれの有意義な表現がNote コンストラクタ.Src sは、解析された式(例えばソーステキストと線とコラム番号)に関するメタデータ情報を含みます.たとえば、次の式があります.
    1 + 2
    
    それは何か(このように)解析されます.
    Note (source code info of the "1 + 2" expression)
      (NaturalPlus -- for the `+` operator
        (Note (source code info for the "1") (NaturalLit 1))
        (Note (source code info for the "1") (NaturalLit 2))
      )
    
    その他Note , ソースコードについての余分な情報を保持する他のコンストラクターがあります.それは主にdhall format and dhall lint コマンド.

    コードレンダラ
    今の質問は、どのように操作することができます/トラバースExpr 次のHTMLを生成する方法では?
  • 名前の宣言と使用
  • 輸入品
  • 残りのコードは、まったく影響を受けません.
  • 最初にこの単純なデータ型を作り始めました.
    -- we will come to the ??? later
    data NameDecl = NameDecl Src Text ???
    
    data SourceCodeType
        = ImportExpr Dhall.Import
        | NameUse NameDecl
        | NameDeclaration NameDecl
    
    data SourceCodeFragment = SourceCodeFragment Src SourceCodeType
    
    あなたはそれぞれを考えることができますSourceCodeFragment ソースコードの次のセクションとして.トークンに似ています(しかし、厳密にはそうではありません).一人一人が差別されるSourceCodeType , これはHTMLでコードをどのようにレンダリングするかの情報を持っています.
    今、私たちはExpr Src Import[SourceCodeFragment] そしてそれはfragments 機能が来る
    fragments :: Expr Src Import -> [SourceCodeFragment]
    
    の実装fragments 関数は、このポスト(正確にするために、110行)に収まるのに非常に大きいです、しかし、それがすることは、生成するためにASTで単純な文脈分析を走らせることですSourceCodeFragment 名前が宣言され使用されるたびに.名前が宣言されていないとき、名前が未使用で、そのHTMLにハイライトが表示されていない場合SourceCodeFragment )
    すべての仕事はinfer 関数、内部宣言fragments :
    -- explanation for ??? will come up later
    infer :: Dhall.Context NameDecl -> Expr Src Import -> Writer [SourceCodeFragment] ???
    
    Dhall.Context ASTを横断するとき、名前を挿入して、問い合わせることができる文脈テーブルです.それが意味をなすとき、我々は名前を挿入します(すなわち、結合、記録フィールドとラムダパラメタをさせてください).
    この作品はすべて許されるdhall-docs 次の定義にジャンプします.
  • レットバインディング
  • ラムダ引数名
  • レコード(リテラルと型)フィールド名
  • インポート(相対的かつリモートのインポート)
  • 見ることができるhere デモ.テストケースには、そこに表示されている機能があります.

    について??? コードで?
    LET結合とラムダ引数名は同じJTD論理を共有します:
  • 結合名のソース範囲の位置を計算する
  • 両方の兄弟表現を横断するLet and Lam それに応じてコンテキストに名前を追加する.
  • ただし、レコードフィールドのサポートを追加すると少し異なります.セレクタ式のフィールドが定義されている名前を強調したい.次の例では、ハイライトしたいfoo ユーザーがfoo セレクタ式a.foo :
    let a = { foo = 2 }
    in  a.foo
    
    プッシュfoo 文脈へのラベルは正しい解決ではありませんfoo ラベルは複雑です.
    代わりに、別のデータ型を使用することです.
    data JtdInfo
        = RecordFields (Set.Set NameDecl)
        | NoInfo
    
    ??? 対応JtdInfo . あなたは考えることができますJtdInfo 余分な情報としてより正確に生成されたJTDを支援する静的解析から回復した.置換??? 上のスニペットでは、
    data NameDecl = NameDecl Src Text JtdInfo
    
    infer :: Dhall.Context NameDecl -> Expr Src Import -> Writer [SourceCodeFragment] JtdInfo
    
    現在NameDecl 関連付けがあるJtdInfo , を返しますJtdInfo からinfer . 今、我々はパターンにマッチすることができますField JTDを正しく設定するコンストラクタ
    Field e (FieldSelection (Just Src{srcEnd=posStart}) label (Just Src{srcStart=posEnd})) -> do
      fields <- do
        dhallType <- infer context e
        case dhallType of
          NoInfo -> return mempty
          RecordFields s -> return $ Set.toList s
    
      let src = makeSrcForLabel posStart posEnd label
      let match (NameDecl _ l _) = l == label
      case filter match fields of
        x@(NameDecl _ _ t) : _ -> do
          Writer.tell [SourceCodeFragment src (NameUse x)]
          return t
        _ -> return NoInfo
    
    それが意味するならば、我々はJTDをセットアップします.感謝のパターンマッチングは、エレガントなすべてのケースを扱うことができます.

    終わる
    これは私がGSoCプロジェクトの一環として開発した最後の機能です.すべてのプロジェクトと私のすべての経験についての話の概要についての私の最後のレポートを読むために調整してください.
    読書ありがとう!