Avatar
Kishikawa Katsumi 4/21/2019 1:49 PM
@Yuta Saito 1年前を思い出しながらSwiftPowerAssertから-dump-astのParserの部分を独立させて、現行の環境で動くようにしました。よかったら参考にしてください。 https://github.com/kishikawakatsumi/SwiftAST 使い方は swift-ast parse [FILE_PATH_TO_PARSE] -buildCommand [BUILD_COMMAND_TO_BUILD_THE_PROJECT] 例えば path/to/swift-ast parse "./Framework/Sources/SpreadsheetView.swift" -buildCommand xcodebuild -scheme SpreadsheetView や、 path/to/swift-ast parse ./Lib/KeychainAccess/Keychain.swift -buildCommand xcodebuild -scheme KeychainAccess -sdk iphonesimulator -destination "name=iPhone Xs,OS=12.2" です。 以下、思い出したことを書きます。 (edited)
Experimental project for parsing an output that swift -dump-ast produces - kishikawakatsumi/SwiftAST
👀 1
1:56 PM
-dump-astは全ての型が解決しているので原則としてコンパイルが成功する必要があります。簡単な(依存関係がない)ファイルなら簡単ですが、他のファイルやモジュールを参照している場合は同じモジュールのファイルの場合はそれを一緒に渡す、別モジュールの場合はそれを先にビルドする必要があります。 なので、時間は余計にかかるのですが、 https://github.com/kishikawakatsumi/SwiftAST/blob/master/Sources/SwiftAST/XcodebuildTool.swift#L37-L42 のように swift -dump-ast を実行する前にまずビルドしています。 ビルドエラーが起こる状態で swift -dump-ast を実行した場合、単に失敗するか不完全なASTが出力されます。 swift -dump-ast の出力自体が謎であるのにさらに不完全なASTを相手にするのは無理なので、最初のキモは確実にビルドできるパラメータを組み立てることです。 (edited)
Experimental project for parsing an output that swift -dump-ast produces - kishikawakatsumi/SwiftAST
2:01 PM
最初のハードルを超えたら、完全なASTのテキストが手に入るので、それを読んでいくだけです。 難しいのは constructor_declbrace_stmt assign_expr といったノードの種類がよくわからないことと、それぞれがどういう属性を持っているかが当然Undocumentedでよくわからないところで、Parse自体はそれほど難しくないはず(目的にもよりますが)です。
2:05 PM
https://github.com/kishikawakatsumi/SwiftAST/blob/master/Sources/SwiftASTCore/SwiftAST.swift#L39-L46 ^ を見ると私がどのようにやったかがわかります。 private func process(sourceFile: URL, verbose: Bool = false) throws -> AST { let arguments = buildArguments(source: sourceFile) let rawAST = try dumpAST(arguments: arguments) let tokens = tokenize(rawAST: rawAST) let node = lex(tokens: tokens) let root = parse(node: node) return root } dumpAST(arguments:)swift -dump-astを実行します。 得られたテキストをtokenize(rawAST:)でトークンに分割して、lex(tokens:)でツリー構造を作ります。 最後にparse(node:) でツリー構造のデータに意味を持たせます。
Experimental project for parsing an output that swift -dump-ast produces - kishikawakatsumi/SwiftAST
2:09 PM
-dump-astの出力はS式のように見えるのでカッコを頼りにしたくなりますが、別の意味のカッコが普通に("'に囲まれることなく)出てくるのでそれは難しいです。 代わりに行とインデントを使います。
2:12 PM
func tokenize(source: String) -> [ASTToken] { var lines = [String]() for line in source.split(separator: "\n", omittingEmptySubsequences: false) { let trimmed = line.trimmingCharacters(in: .whitespaces) if trimmed.hasPrefix("(inherited_conformance") || trimmed.hasPrefix("(normal_conformance") || trimmed.hasPrefix("(abstract_conformance") || trimmed.hasPrefix("(specialized_conformance") || trimmed.hasPrefix("(assoc_type") || trimmed.hasPrefix("(value req") || !trimmed.hasPrefix("(") { continue } lines.append(String(line)) } ... -dump-astの出力をまず行ごとに分割します。このとき、いくつかのプロジェクトでノイズになる行(実行コードには関係なく見える上にインデントがおかしい)が発見されたのでそれを取り除いています。
2:17 PM
そのあと、1行ごとに1文字ずつIterateしながらトークンに分割していきます。 私の場合はindent(先頭の空白)、symbol(シングルクオートで囲まれた文字列)、string(ダブルクオートで囲まれた文字列)、token(それ以外の文字列や記号)に分類しました。 class ASTToken { enum TokenType { case token case symbol case string case indent(Int) }