Open ufcpp opened 4 years ago
単体テストの場所を特定するところから。
とりあえず、 CharacterInfo.IsValidIdentifier
は /define:SYMBOL
オプションのパースで呼ばれてて、CommandLineTest.cs
から呼ばれてること確認済み。
この辺りにサロゲートペアなシンボル渡すテストを足してみる。
Visual Studio for Windows が .NET Framework 上で動いてるせいで Unicode 8か9 辺りで止まってる可能性高いみたい。
U+9FD6~U+9FFC 辺りの文字(Unicode 10.0~13.0 で追加された漢字。Other Letter)のコンパイルに失敗する。 ↓ここの辺りの文字。
この理屈だと、dotnet コマンドでビルドすると通るけど VS for Win ではコンパイルできない C# コードになってるかも?
ほんとになった。VS でエラーになって dotnet run でエラーにならない。
Swift はかなり意図的に「ほぼ何でも」っぽい。 https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412
「BMP の non-combining な alphanumeric と、私用面じゃない non-BMP 全部」って書いてある。
cref の中は TryScanXmlEntity も対応が必要かもしれない。
&#x...
みたいなやつ。
例えば、cl\u0061ss
が class
扱いになって有効な識別子になる。
とりあえず放送中に当たりが付いたことまとめ:
今日、見つけた単体テストプロジェクトは2か所
csc /define
オプションが絡むこれ以外に、たぶん Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests も関係すると思う。 (参照した dll のメタデータ中にサロゲートペアを含む場合)
具体的な単体テストクラス:
csc /define:SYMBOL
のテストは CommandLineTests
#define SYMBOL
のテストは PreprocessorTests
<see cref="Identifier" />
のテストは CrefLexerTests
var identifier = ...;
のテストは LexicalTests
LexicalTests
はさすがにそこそこしっかりしてて、「この名前は識別子にできない」みたいなテストもあるし、非 ASCII 文字のテストもある。
\U
のテストもあるけど、\U00001234
しかテストデータがない(当然、サロゲートペアのテストはない)。
cref, directive のテストは割とガバガバ…
U+1234 は見るからに適当な値だけど、エチオピアの文字らしい。有効な letter。
http://eel.is/c++draft/lex.name#1
Swift のは C++ に沿ってるっぽい?
GCC は2015年くらいからこの仕様認めてそう。
確かでも、MS の C++ は UAX#31 だったような… ← MS C++ も上記仕様だった
一方で、C++ 23 目標で C++ も UAX#31 に従うべき見たいな提案見かけた。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1949r1.html
C++/Swift のルールがどこから来たのか気になってたけど、「John looked at Unicode and cobbled the list of valid ranges manually.」(Unicode を眺めてとりあえず手動で範囲を決めた)とか書かれてて受けるww
p1949r1 の提案、UAX#31 の問題もきれいにまとまってるなぁ:
https://unicode.org/reports/tr31/#Immutable_Identifier_Syntax 旧 (現) C++ ルールは Immutable Identifiers って言うらしい。 一応、Unicode バージョンに依存しないって意味ではメリットあるみたい。 white space, private use, surrogate, control っていう「追加しない」って言われてるカテゴリーをベースに決めてそう。
ただ、これを使うとセキュリティガバガバだから推奨はされてないとか。
ここを surrogate-pair-identifier の作業メモに使うのもどうかと思いつつ…
今、抜いてるテストケースいくつか:
"a\uD800", "\uD800a", "a\uDF00", "\uDF00a", "\uD800\u200D\uDF00", // ZWJ between a valid surrogate pair 𐌀 (Old Italic A)
// Surrogate Pairs "\U000104A0\U000104A1", // 𐒠𐒡 (Osmanya Digit) "\U00020000\U0001F600\U00020000", // (Emoji between Letters) "\U0001D7D8\U0001D538", // double-struck 0A
影響を及ぼしそうな(テストの追加が必要そうな)個所:
WithCrefTypeParametersBinder.AddTypeParameters CSharpCommandLineParser.ParseConditionalCompilationSymbols CSharpCommandLineParser.ParseAssemblyReferences CSharpParseOptions.ValidateOptions PEMethodSymbol.ComputeMethodKind ISymbol.CanBeReferencedByName (Symbol, PreprocessingSymbol) SourceMethodSymbolWithAttributes.ValidateConditionalAttribute SourceNamedTypeSymbol.ValidateConditionalAttribute SourcePropertySymbol.ValidateIndexerNameAttribute
配信すると決めたわけじゃないけど、メモ書き。 というか、配信で収まるメモ書き量じゃなくなってる気がする。
Unicode サロゲートペア対応
C# の識別子をサロゲートペアに対応させたいという話。
作業 branch: https://github.com/ufcpp/roslyn/tree/surrogate-pair-identifier
サロゲートペア
UTF-16 で1ユニットにならないやつ(サロゲートペアになるやつ)。 なんていうのが正式なのかいまだにわからず。
90年代~2000年前後の言語は内部的に UTF-16 で文字列を持ってるせいで、これに対応するのが大変。
C# もまさに。
C# の lexer 仕様
C# 言語仕様 lexical-structure
識別子として使える文字は Unicode カテゴリーで指定してる。 サロゲートペア文字にもちゃんとカテゴリーが割り当たってるのに、C# はこれをちゃんと見れてない。
エジプトヒエログリフ、楔形文字、非常用漢字とかが該当。「𩸽」とかは本来識別子として使えるはずだけど、C# では使えない。
補足: 他の言語
同じ「UTF-16 世代な言語」である Java は対応してる。 JavaScript も Chrome/Edge のやつは対応してる(IE のは対応してない)。 PHP も対応してそう(Web向け言語だし結構前から内部的に UTF-8 かも)。
もっと後に出てる「UTF-8 世代な言語」である Go は当然対応してる。 Python も 3 系は UTF-8 らしく、対応してそう。
Rust は潔くて、元からローマンアルファベット(ASCII の範囲内)しか認めてない(言語仕様に明記)。 Swift もある意味潔くて、U+10000 以上の文字は全部受け付けてる(これも明記)。
複雑なの、識別子だけ
特に、 unicode_escape_sequence が厄介。
補足: 他、Unicode 関係しそうなところ
改行はカテゴリー指定じゃなくて、1文字1文字指定。
空白には Zs カテゴリーが出てくるけど、これは特に追加されてる文字ない。全部 U+3000 以下。
Identifier 的なものの lex 処理、3系統ある
参考: XML の lexer 仕様
XML の lexer 仕様
U+10000 以上全部受け付けてる。 Swift はもしかしたらこれに倣ってるのかも?(要確認)
特に面倒なのが unicode_escape_sequence
↓が有効な C# コード。
↑の Visual Studio 上での見た目:
SlidingTextWindow.ScanUnicodeEscape
で処理してる。\uXXXX
(16進数4桁)と\UXXXXXXXX
(16進数8桁)がある\U
の方は今も対応してる\u
でサロゲートペアからの復元には対応してない(すべき?Java はしてない)