Avatar
Kishikawa Katsumi 4/21/2019 2:25 PM
この段階でテキストがトークンに分割されたものが手に入ります。 ▿ 1498 elements ▿ 0 : ( ▿ 1 : source_file ▿ 2 : "/Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift" ▿ 3 : __ ▿ 4 : ( ▿ 5 : struct_decl ▿ 6 : range ▿ 7 : = ▿ 8 : /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:1:8 - line:17:1 ▿ 9 : "Country" ▿ 10 : interface ▿ 11 : type ▿ 12 : = ▿ 13 : 'Country.Type' ...
2:26 PM
ASTLexer がこのトークンの配列をツリー構造にします。Lexerといってますが、ツリー構造に変換するだけです。
2:29 PM
[(, source_file, "/Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift"] [(, struct_decl, range, =, /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:1:8 - line:17:1, "Country", interface, type, =, 'Country.Type', access, =, public, non-resilient] [(, pattern_binding_decl, range, =, /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:2:12 - line:2:22] [(, pattern_typed, type, =, 'String'] [(, pattern_named, type, =, 'String', 'code', )] [(, type_ident] [(, component, id, =, 'String', bind, =, Swift.(file).String))))] [(, var_decl, range, =, /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:2:16 - line:2:16, "code", type, =, 'String', interface, type, =, 'String', access, =, public, let, readImpl, =, stored, immutable] [(, accessor_decl, implicit, range, =, /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:2:16 - line:2:16, 'anonname=0x10a8205a8', interface, type, =, '(Country) -> () -> String', access, =, public, get_for, =, code] [(, parameter, "self", interface, type, =, 'Country', )] [(, parameter_list, )] [(, brace_stmt, implicit, range, =, /Users/katsumi/Documents/XcodeProjects/SwiftPowerAssert/Fixtures/Atlas/Sources/Atlas/Atlas.swift:2:16 - line:2:16] [(, return_stmt, implicit] ... こんな感じです。これでツリー構造とノードの種別や属性情報がプログラムから扱える形になりました。
2:36 PM
ここまではそんなに難しくなくて、大変なのはソースコードから生成されるASTが難しいので、この情報を使っていくところです。 わかりやすいところでstruct_declとかvar_declcall_exprmember_ref_exprdot_syntax_call_exprなどです。それ以外に膨大な種類のノードがあるので、全部に対応するのはとても大変なので、必要に応じてノードをフィルタして使っていく、というのが良いかと私は考えています。
2:41 PM
その必要なところだけ使う、というのをやっているのがASTParserで、 private func parseSourceFileNode(node sourceFileNode: ASTNode<[ASTToken]>) -> AST { var declarations = [Declaration]() for node in sourceFileNode.children { for token in node.value { switch (token.type, token.value) { case (.token, "top_level_code_decl"): declarations.append(.topLevelCode(parseTopLevelCodeDeclarationNode(node: node))) case (.token, "import_decl"): declarations.append(.import(parseImportDeclarationNode(node: node))) case (.token, "struct_decl"): declarations.append(.struct(parseStructDeclarationNode(node: node))) case (.token, "class_decl"): declarations.append(.class(parseClassDeclarationNode(node: node))) case (.token, "enum_decl"): declarations.append(.enum(parseEnumDeclarationNode(node: node))) case (.token, "extension_decl"): declarations.append(.extension(parseExtensionDeclarationNode(node: node))) case (.token, "func_decl"): declarations.append(.function(parseFunctionDeclarationNode(node: node))) default: break } } } return AST(declarations: declarations) } のようにまずStructやClass宣言を拾って、そのあと例えば関数だったら
2:41 PM
private func parseFunctionDeclarationNode(node: ASTNode<[ASTToken]>) -> FunctionDeclaration { let tokens = node.value let name = parseString(tokens: tokens) ?? parseSymbol(tokens: tokens) let accessLevel = parseAccessLevel(tokens: tokens) var parameters = [Parameter]() var body = [Statement]() for node in node.children { for token in node.value { switch (token.type, token.value) { case (.token, "parameter_list"): parameters.append(contentsOf: parseParameterListNode(node: node)) case (.token, "brace_stmt"): body.append(.expression(parseExpressionNode(node: node))) default: break } } } return FunctionDeclaration(accessLevel: accessLevel, name: name!, parameters: parameters, body: body) } パラメータやステートメントをさらに拾っていく、としています。 一番大変なのがこのParser部分を作っていくところだと思います。ここは自分が利用する情報を探しながらトライアルアンドエラーで書いていくことになります。