Skip to content

Instantly share code, notes, and snippets.

@kitasuke
Created August 3, 2019 12:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kitasuke/e8a9e10068ce17b2d69ab0b411885f05 to your computer and use it in GitHub Desktop.
Save kitasuke/e8a9e10068ce17b2d69ab0b411885f05 to your computer and use it in GitHub Desktop.
StringLiteralExpr-jp

Swift 5.1へのStringLiteralExprの改善

こんにちは。iOSエンジニアの@kitasukeです。

今回は、Swift Compilerのパーサー内部でlibSyntaxが作成する、StringLiteralExprに関する改善を行ったので、その内容を簡単に紹介します。
ちなみに、この変更はlibSyntax内での変更でありSwiftのString APIへの変更ではありません。

概要

libSyntaxには、Stringリテラル用にStringLiteralExprStringInterpolationExprの2種類のシンタックスが定義されています。 しかし、両者のリテラル間で文字列を囲むクオートの扱いに差異がありました。 その結果、生成されたシンタックスから文字列のみを取得する実装が共通化できない問題がありました。

詳しい内容は下記のチケットを参照してください。
https://bugs.swift.org/browse/SR-10241

出力結果の比較

それでは実際にシンタックスを出力してみましょう。
swift -frontend -emit-syntaxで出力するlibSyntaxのシンタックスツリーは情報量が多いので、今回は代わりにSwiftSyntaxを使います。 SwiftSyntaxlibSyntaxをSwift APIで提供しているツールです。 SwiftSyntaxの詳細について興味がある方は、こちらの本を参考にしてください。 https://kitasuke.booth.pm/items/1310632

今回はSwift AST Explorerを使ってSwiftSyntaxの出力結果を表示します。

StringLiteralExprの場合

"foo"を入力した結果は、以下のように文字列がクオートに囲まれてます。 文字列のみ取得したい場合は、自分でクオートを除去する必要があります。

- StringLiteralExpr
    - \"foo\"

StringInterpolationExprの場合

"foo \(bar)"を入力した結果は、以下のように文字列とクオートが別トークンとして表現されます。文字列のfooを取得したい場合は、StringSegmentの値だけ使えば良いです。

- StringInterpolationExpr
    - "
    - StringInterpolationSegments
        - StringSegment
            - foo
        - ExpressionSegment
            - \
            - (
            - IdentifierExpr
                - bar
            - )
    - "

解決方法

上記の比較結果から、文字列とクオートの関係性においてStringInterpolationExprの方がより良い構造だと分かります。 したがって、StringInterpolationExprの構造をStringLiteralExprにも適用できれば問題が解決するはずです。

現状の定義

こちらが現状のStringLiteralExprの定義です。 StringLiteralExprの要素はStringLiteralのみです。

utils/gyb_syntax_support/ExprNodes.py

Node('StringLiteralExpr', kind='Expr',		
      children=[		
          Child("StringLiteral", kind='StringLiteralToken')		
      ]),

新しい定義

こちらが新しいStringLiteralExprの定義です。 StringLiteralExprの要素はOpenQuote, Segments, CloseQuoteがあります。

この新しい定義によって、StringLiteralExprStringInterpolationExprを同じ構造にするために、一つのシンタックスに統合されます。

utils/gyb_syntax_support/ExprNodes.py

Node('StringLiteralExpr', kind='Expr',
      children=[
          Child('OpenQuote', kind='Token',
                token_choices=[
                    'StringQuoteToken',
                    'MultilineStringQuoteToken',
                ]),
          Child('Segments', kind='StringLiteralSegments'),
          Child('CloseQuote', kind='Token',
                token_choices=[
                    'StringQuoteToken',
                    'MultilineStringQuoteToken',
                ]),
      ]),

SwiftSyntax上の定義の違いはこちらで解説をしています。 興味がある方は参考にしてみてください。

https://medium.com/@kitasuke/swiftsyntaxのstringliteralexprsyntaxとstringinterpolationexprsyntaxの違いについて-c10520adfdf6

修正後の出力結果

上記の新しい定義を使って"foo"SwiftSyntaxで出力すると、下記のようにクオートと文字列が区別されています。

- StringLiteralExpr
    - "
    - StringLiteralSegments
        - StringSegment
            - foo
    - "

"foo \(bar)"の出力結果は先程と同じなので省略します。

両リテラルで構造が統一され、より良い階層でトークンが構成されるようになりました。 したがって、どちらのリテラルでも文字列を取得したい場合は、StringSegmentの値を見れば良いです。

詳しい実装内容はこちらのPRをご覧ください。

apple/swift#24718

まとめ

今回のように小さな変更でも大いに便利になることもあります。 良い機会なのでパーサーの仕組みをもっと理解できるようにソースコードを読んでみようと思いました。

今回のPRは、@rintaroさんが丁寧に色々アドバイスしてくれたおかげなので感謝してます!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment