kzrnm / ac-library-csharp

42 stars 5 forks source link

Expanderを実装 #30

Closed kzrnm closed 4 years ago

kzrnm commented 4 years ago

21 について、Expanderを実装してみました。

PowerShell CoreがRoslynを標準搭載しているのでPythonより適しているかと思います。 WindowsとMacのPowerShell Core 7.0.3で動作確認しました。

AtCoderProgram

コンテスト時にコードを書くのを想定したプロジェクトです。

C# 8.0、unsafe許可、System.Runtime.CompilerServices.Unsafe(4.7.0)などはAtCoderの環境にあわせています。

このPull requestではProgram.csを含めていますが、実際には含めない方が良いかもしれません。

オプションについて

公式のexpander.pyに合わせて、ファイルではなく標準出力へ書き出すオプションもつけています。

また、AtCoderLibraryの参照が必須なのでDebugかReleaseのどちらかを選ぶようにしています。どちらでも動くのであまり意味はないです。

kzrnm commented 4 years ago

コードの説明

AtCoderLibraryの参照を追加

$csprojPath = "$PSScriptRoot/AtCoderLibrary/AtCoderLibrary.csproj"

$buildType = "Debug"
if ($UseRelease) { $buildType = "Release" }

$target = @(([xml](Get-Content $csprojPath)).GetElementsByTagName('TargetFramework'))[0].InnerText
$atcoderlibPath = "$PSScriptRoot/AtCoderLibrary/bin/$buildType/$target/AtCoderLibrary.dll"

if (-not (Test-Path $atcoderlibPath) ) {
    Get-Command dotnet -ErrorAction SilentlyContinue | Out-Null
    if ($?) {
        dotnet build "$csprojPath" -c "$buildType"
    }
    else {
        Write-Error "You need dotnet command."
        exit
    }
}
  1. AtCoderLibrary.dll(デフォルトでは"$PSScriptRoot/AtCoderLibrary/bin/Debug/netcoreapp3.1/AtCoderLibrary.dll")があるか確認。
  2. なければ、dotnet buildでビルドする。dotnetコマンドが見つからない場合はしょうがないので終了する。

function Merge-Hashtable

文字通りHashtableをマージする。

function Get-SemanticModel

SyntaxTreeからSemanticModelを取得する。ここでAtCoderLibrary.dllが必要。

function Get-AtCoder-Method-Symbols

メソッド呼び出しを取得する。

function Split-Code

ソースコード文字列をusingディレクティブと残りに分割する

function Expand-AtCoder-Code

ロジックの本体

aclpath

    $aclpath = Merge-Hashtable (
        Get-ChildItem "$PSScriptRoot/aclpath.*.json" | ForEach-Object { 
            Get-Content $_  | ConvertFrom-Json -AsHashtable
        })

aclpath.*.jsonファイルからクラス名とファイルの対応関係を取得する。aclpath.*.jsonが複数あるならマージしておく。

expand

            if ($aclpath.ContainsKey($className)) {
                $updated = $true
                foreach ($file in $aclpath[$className]) {
                    if ($addedFiles.Add($file)) {
                        Write-Debug $file
                        $headUsing, $bodies = (Split-Code @(Get-Content $file))
                        $code = ($headUsing -join $lineBreak) + $lineBreak + $code + $lineBreak + ($bodies -join $lineBreak) 
                    }
                }
                $aclpath.Remove($className)
            }

expand部分。aclpath.*.jsonを元に必要なファイルを持ってくる。usingディレクティブは本体より前、残りは後ろにつける。 $aclpath.ContainsKey($className)$trueであるかぎり、ソース解析→追加→……とループすることで必要なファイルを再帰的に追加する。

重複するusingを削除

    $lines = $code.Split($lineBreak)
    $headUsing, $bodies = (Split-Code $lines)
    $headUsing = [System.Collections.Generic.List[string]]$headUsing
    $headUsing.Sort([System.Comparison[string]] { param($a, $b); [System.StringComparer]::Ordinal.Compare($a.Trim(';'), $b.Trim(';')) })
    $headUsing = $headUsing | Get-Unique

usingディレクティブは重複しても問題ないが見た目が長くなってしまうので重複するものは削除する。 PowerShell標準のソートだと大文字小文字区別するソートができないのでList<T>のソートを使う。

key-moon commented 4 years ago

実装ありがとうございます!

別途 powershell が必須なことに関して、技術選定をもう少しした上で確定する必要があると感じています。自分で実装するつもりでいたため、仕様等をあまり議論していなかったのがとても申し訳ないです。

また、依存関係を記した json ファイルを追加するのは保守コストの観点からも慎重になりたいという気持ちはあります。

現段階では一旦確認は保留とさせて頂き、 #21 でもう少し議論を重ねたいです。本当に申し訳ありません。

kzrnm commented 4 years ago

保留中ですが機能改修です。