スマフォ開発チームの高地です。
iOS開発界隈ではSwiftに注目が集まっています。 iOS8からはObjective-Cから解放されることで、喜んでいる開発者も多いのではないでしょうか。 ということで、今回はSwiftのREPL機能についてお話しします。
Swiftの2つのREPLモード
SwiftにはREPLが用意されているので、 Ruby、Python、Node.js等のLL言語のように簡単に動作確認ができます。 ところがSwift --helpをみていたところ、実はREPLモードが2つあることにきがつきました。
- -integrated-repl Integrated REPL mode
- -repl REPL mode
たぶん、-replは普通一般的なREPLモードだと思われます。それでは-integrated-replモードとはどんなことができるのでしょうか? 調査環境はXcode6-Beta2です。
SwiftのREPL:-integrated-replモード
まずは-integrated-replモードに切り替え、対話モードに入ります。実際にこのモードのオプションはどのようなものがあるのか:helpでたたいてみました。
:quit - quit the interpreter (you can also use :exit or Control+D or exit(0)) :autoindent (on|off) - turn on/off automatic indentation of bracketed lines :constraints debug (on|off) - turn on/off the debug output for the constraint-based type checker :dump_ir - dump the LLVM IR generated by the REPL :dump_ast - dump the AST representation of the REPL input :dump_decl - dump the AST representation of the named declarations :dump_source - dump the user input (ignoring lines with errors) :print_decl - print the AST representation of the named declarations :print_module - print the decls in the given module, but not submodules
-integrated-replモードには9つのオプションが用意されています。 :quite、:autoindentは想像がつくので省略し、1つずつ見てみましょう。
constraints debug
:constraints debug (on|off) - turn on/off the debug output for the constraint-based type checker
XcodeのConstraintsのためのデバッグオプションの設定のようです。 ここでon, offをきりかえて、一般的なREPL modeでデバッグを行うのでしょうか・・・デバッグ環境を用意するのも手間なので、これ以上は時間をかけないでおきます。
dump_ir
:dump_ir - dump the LLVM IR generated by the REPL
LLVMのIR(Intermediate Representation)ということで、LLVMでコンパイルされた中間コードを出力するようです、実際にREPLに入力してみます。MoenyForwardという文字列をdataというmutableな変数に代入するだけという1行コードです。
(swift) var data:String = "MoneyForward" // data : String = "MoneyForward"
たったこれだけではありあますが:dump_irで出力させてみると
swift) :dump_ir ; ModuleID = 'REPL' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-darwin13.2.0" %0 = type <{ %1 }> %1 = type <{ %2, %3, %4 }> %2 = type <{ i8* }> %3 = type <{ i64 }> %4 = type <{ [8 x i8] }> %5 = type { i8**, %6 } %6 = type { i64 } %7 = type { void (%8*)*, i8**, %6 } %8 = type { %6*, i32, i32 } %9 = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i32, i32 } %10 = type <{}> %11 = type { [24 x i8], %6*, i8** } %12 = type { i8*, %8* } %13 = type <{ %12 }> %14 = type { [24 x i8], %6*, i8** } %15 = type <{ [40 x i8], [1 x i8] }> %16 = type <{ [40 x i8], [1 x i8] }> %17 = type <{ [40 x i8], [1 x i8] }> %18 = type <{ [40 x i8], [1 x i8] }> %19 = type { [24 x i8], %6*, i8** } %20 = type { [24 x i8], %6*, i8** } %21 = type { [24 x i8], %6*, i8** } %22 = type <{ i8 }> %23 = type <{ %24, %24 }> %24 = type <{ i8* }> %25 = type { [24 x i8], %6* } %26 = type <{ %27 }> %27 = type <{ %8*, i8*, %6* }> %28 = type <{ i16 }> %29 = type { %objc_object* } %30 = type { %objc_object* } %31 = type <{ [8 x i8] }> %32 = type <{ %24, %33 }> %33 = type <{ i64 }> %34 = type <{ %35 }> %35 = type <{ %36 }> %36 = type <{ [8 x i8] }> %swift.type_pattern = type opaque %objc_object = type opaque %swift.opaque = type opaque %CSs17HeapBufferStorage = type opaque @_Tv4REPL4dataSS = global %0 zeroinitializer, align 8 @0 = private unnamed_addr constant [13 x i16] [i16 77, i16 111, i16 110, i16 101, i16 121, i16 70, i16 111, i16 114, i16 119, i16 97, i16 114, i16 100, i16 0] @1 = private unnamed_addr constant [20 x i16] [i16 47, i1 (省略)・・・ ;
かなりアセンブリチックな中間コードが約6000行ほど表示されました。 グローバルIDやローカルID(%swift.opaque)などが大量に出力されているのがわかります。 まさしく中間コードが出力されました。たった1行の宣言コードでしたが、コンパイルするための最適化のためでしょうか、非常に大量のコードが生成されているようです。
dump_ast
:dump_ast- dump the AST representation of the REPL input
これも上の:dumo_irと似ている印象を受けますが、コンパイル過程のAST(抽象構文木)が出力されます。
swift) :dump_ast (source_file (top_level_code_decl (brace_stmt (tuple_expr type='()' location=:1:5 range=[:1:5 - line:1:6])) (top_level_code_decl (brace_stmt (pattern_binding_decl (pattern_typed type='String' (pattern_named type='String' 'data') (type_ident (component id='String' bind=type))) (call_expr implicit type='String' location=:1:19 range=[:1:19 - line:1:19] (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19] (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no) (type_expr implicit type='String.Type' location=:1:19 range=[:1:19 - line:1:19] typerepr='<>')) (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:19 range=[:1:19 - line:1:19] encoding=utf16 value="MoneyForward"))) ) (top_level_code_decl (brace_stmt (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] (closure_expr type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] discriminator=0 (brace_stmt (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).print [with T=String] specialized=no) (call_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no) (type_expr implicit type='String.Type' location=:1:1 range=[:1:1 - line:1:1] typerepr='<>')) (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:1 range=[:1:1 - line:1:1] encoding=utf16 value="// data : String = "))) (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).debugPrintln [with T=String] specialized=no) (declref_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).top-level code.explicit closure discriminator=0.arg@:1:1 specialized=no)))) (paren_expr type='(String)' location=:1:1 range=[:1:1 - line:1:1] (load_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] (declref_expr implicit type='@lvalue String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).data@:1:5 specialized=no))))) (var_decl "data" type='String' storage_kind='stored'))
まだ、ASTのほうが、コード解析段階なので、人間の目にやさしいです。行数も36行程度なので、そこまで苦労せずにリーディングできそうです。
(declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no)
このあたりみると、内部的にUTF-16に変換していることが推測できます。
dump_decl
:dump_decl - dump the AST representation of the named declaration
は、AST内で定義されている変数をnameに指定することで、内容が確認できます。上記で書いたdata変数を指定してみると
(swift) :dump_decl data (var_decl "data" type='String' storage_kind='stored')
とAST内での解析されたdata変数の内容が出てきます。var_declが変数定義を表し、typeが型、storage_kindはなんでしょう?変数が参照渡しなのか、値渡しを表しているのでしょうか、、、ちょっと不明ですね。
dump_source
:dump_source - dump the user input (ignoring lines with errors)
これは、ユーザーが入力したソースコードが出力されます。そのままですね。
(swift) :dump_source var data:String = "MoneyForward"
そのまま表示されました。
print_decl
:print_decl - print the AST representation of the named declarations
は、:dump_declオプション時よりももっと簡略的に出力されるようです
(swift) :print_decl data var data: String
こちらはほぼ、素のSwiftコード定義に近い出力結果となりました。
:print_module - print the decls in the given module, but not submodule
は、Swiftで作成されたモジュールの内容を出力してくれるようです。実際にモジュールを作成してみればよいのですが、うまく作成できませんでした。何かよいモジュールはないか、色々とためしたところ、Swift自身を指定できるみたいです。
print_module swift
(swift) :print_module swift
を行うとSwift内部で定義されている、構造体、Extensions、プロトコル、など基底となる構造がずらずらとでてきます。何個かピックアップしますと、
struct UnsafePointer : BidirectionalIndex, Comparable, Hashable, LogicValue { var value: RawPointer init() init(_ value: RawPointer) init(_ other: COpaquePointer) init(_ value: Int) init(_ from: UnsafePointer) static func null() -> UnsafePointer static func alloc(num: Int) -> UnsafePointer func dealloc(num: Int) var memory: T { @transparent get {} @transparent set {} } func initialize(newvalue: T) func move() -> T func moveInitializeBackwardFrom(source: UnsafePointer, count: Int) func moveAssignFrom(source: UnsafePointer, count: Int) func moveInitializeFrom(source: UnsafePointer, count: Int) func initializeFrom(source: UnsafePointer, count: Int) func initializeFrom(source: C) func destroy() func destroy(count: Int) var _isNull: Bool { @transparent get {} } @transparent func getLogicValue() -> Bool subscript (i: Int) -> T { @transparent get {} @transparent set {} } var hashValue: Int { get {} } func succ() -> UnsafePointer func pred() -> UnsafePointer @conversion @transparent func __conversion() -> CMutablePointer func __conversion() -> CMutableVoidPointer @conversion @transparent func __conversion() -> CConstPointer @conversion @transparent func __conversion() -> CConstVoidPointer @conversion @transparent func __conversion() -> AutoreleasingUnsafePointer init(_ cp: CConstPointer) init(_ cm: CMutablePointer) init(_ op: AutoreleasingUnsafePointer) init(_ cp: CConstVoidPointer) init(_ cp: CMutableVoidPointer) }
ポイントをキャストするためのUnsafePointer構造体や
extension Dictionary<KeyType, ValueType> : Printable, DebugPrintable { func _makeDescription(#isDebug: Bool) -> String var description: String { get {} } var debugDescription: String { get {} } } extension Dictionary<KeyType, ValueType> : Reflectable { func getMirror() -> Mirror }
Dictionaryなどなどおなじみの構造が確認できます。ここから考えるに-integrated-replにおいて、Swift自身もモジュールというあつかいなのかなと。
ちなみに、対話モードにしなくても、出力結果をコンソール上に出力する方法もあります。shellのechoコマンドで、内部で実行したいオプションをわたしてあげても、同じ結果が得られます。
echo :print_module Swift | xcrun swift -integrated-repl
公式の情報が少ないので、本質的な-integrated-replの使い方はまだわかりませんが、このようにコンパイルする過程の状態(AST、IR)を容易に確認できる手段がREPLで用意されているのというのは驚きでした。Swiftを使う開発者が、より最適なコーディングを理解するための情報を得るツールとしても使えますね。 このような開発ツールの充実度を高めようとしている姿勢からAppleのSwiftにかける本気度を感じました。