SATySFiの文字列の扱いに対する提案
はじめに
この記事はSATySFi Advent Calendar 2022の25日目の記事です。
様々なパッケージを作ったりする過程で、現状のSATySFiでの文字列の仕様について様々な問題点を感じていたため、それにたいしょするためにSATySFiでの文字列の取り扱いを大幅に変更する提案をこの記事で行いたいと思います。
提案内容は後方互換性を破壊するものであるため、v0.1.0に取り込まれることを想定しています。
すでにSATySFiにchar型を導入しようとするプルリクエスト(#290)を作成していましたが、このプルリクエストへのコメントなどを踏まえたうえで今回この記事を書きました。
現在感じている問題点
以下のような問題点を感じています。
- 特殊文字を記述するための構文が機能不十分
- 文字列を記述する構文やプリミティブにおける改行・空白除去のルールが統一されていない
- 「文字」という概念が無い(「文字列」はあるのに)
- 書記素クラスタが無い
- 結合絵文字などの結合する文字列に対処不可能
- 行分割できない箇所で文字列の分割を行ってしまったりする可能性がある
- 各文字のメタデータを取得できない
提案内容
以上の課題を解決するために、以下の修正を行うことを提案します。
対応文字コードの規定と正規化
複雑なコーナーケースをなくすために以下のことを定めます。
- SATySFiが対応する符号化方式はUTF-8のみとする
- 他の符号化方式での入力は未定義動作とする
- SATySFiで取り扱う文字列は事前に正規化を行っておく
- uunfライブラリで正規化を行うことができる
char
型の導入
「文字列」が存在するのに「文字」が存在しないという問題に対応するために、「一文字」を意味するchar
型を導入します。この型は"Unicode characters"に対応させます。
この型のデータに対してはUnicodeで定められているUnicodeスカラー値やその他情報を取得できるようなプリミティブを実装します(必要に応じてその情報に対応する型を新規に作成する必要などがあるかもしれない)。
具体的にはchar-to-unicode-scalar : char -> int
やchar-to-xxx : char -> xxx
のようにプリミティブを作成します。
uucpライブラリを使用することで比較的簡単に実装が可能と思われます。
string
型の定義の変更
string
型をchar list
の糖衣型とします。これにより文字列周りのプリミティブの整理も行えるようになります。
文書記述において文字列という概念の重要度は高いため、廃止はしません。
書記素クラスタの概念の導入
行分割可能位置や結合文字への対応を行うために導入します。
具体的な実装として
- グループごとに分割する
string -> string list
という型のプリミティブを導入する
という方法にすることを考えていますが、これについてはより詳細な検討が必要です。
uusegライブラリを使うことで容易な実装が可能と思われます。
構文の変更
char
型やエスケープ記法の導入のために構文を変更する必要があります。
char
型の構文は主要なプログラミング言語に倣い'a'
のようなシングルクォートで囲む記法とします。型変数の記法とやや被りますが、これはOCamlでも同じであり、使う場面が被らないことやシングルクォートが終端に来るかどうかで容易に見分けが可能であるため、問題が無いと考えます。
前述のchar
型を導入しようとするプルリクエストに対して「char
リテラルを導入しないべき」というコメントがありましたが
- パターンマッチで
char
型を使うためにはパターンマッチでマクロを使えるようにすための変更が必要であり、それが解決策として適切なのかはかなり疑問である(ユーザー定義リテラルによる解決も同様) char
型を扱うのはプログラム層であり、カジュアルなユーザーが記述するマークアップ層ではstring
型しか露出しないため問題がない(もちろん引数にchar
型を要求するコマンドは定義できるが、それはパッケージ作成者の設計思想の話である)- すでにposition情報付きstring記法の導入などリテラルの拡張は行われており、過度に「仕様はコンパクトであるべき」という思想に固執する必要はない
- 文字列処理・組版の文脈において頻出する「一文字」という情報を記述するために難解なマクロ機能への理解が必要であるのは、カジュアルなパッケージ作成への障害となる
などの理由から、新しく記法を導入するべきであると考えています。
シングルクォート単体を表記したり、改行文字などの頻繁に使う特殊文字、Unicodeスカラー値を指定して書いたりするときのためにエスケープ記法も使えるようにします。
などを想定しています。
string
型にも専用の構文を与えます。文書記述において文字列という概念の重要度が高いためです。
文字列記法の構文には
- エスケープ記法が使えるようになってほしい
- エスケープ記法が不要なモードも欲しい
- マークダウンでのコード記法と似ている必要がある
- inline-text中で書くことができるようにする
- 簡単なルールになってほしい
- コードを書く時のようにインデントの除去をしたい場合がある
といった要請があります。そこで、文字列の記法に「モード」の情報を入れられるように構文を変更し、これらに対応したいと思います。
#<mode>`<string>`
という記法にすることで、そのモードによってエスケープ記法や空白の除去などの処理を変更できるようにします。
モードは
- インラインコードモード(デフォルト)(
i
) - ブロックコードモード(
b
) - エスケープモード(
e
)
の3つを用意します。
インラインコードモード
インラインコードモードはデフォルトで、モードを指定せずに
`string`
と書くと
#i`string`
と書いたことになります。
インラインコードモードでは既存の記法のように、
- 前後の改行・空白の除去
- エスケープ無しの記述が可能
となっています。しかし、バッククォートを含ませることはできず、バッククォートが出現した場合はそこで文字列が終了したと認識されます。バッククォートを含ませたい場合は後述のエスケープモードを使用する必要があります。
ブロックコードモード
これはブロックコードを書くときに使うと便利な記法です。
- バッククォート直前・直後の改行の除去
- インデントの除去
を自動で行います。例えば
#b` hoge1 \\ fuga hoge2 `
と書くと
hoge1 \\ fuga hoge2
と評価されます(バッククォート直前・直後の空白行の扱いについては要検討)。これもインラインコードモードと同じく、エスケープ無しの記述(ただし、バッククォートは含ませられない)とします。
エスケープモード
エスケープ記法を使えるようになります。また、バッククォート直後の空白文字が除去されないなどの、一般的なプログラミング言語での文字列記法と同じようになります。エスケープ記法はchar
型で使えるものと共通にします。
例えば
#e` 改行\nしたりバッククォート"\`"を入れたり Unicodeスカラー値で指\u5B9Aしたりできる`
と書くと
改行 したりバッククォート"`"を入れたり Unicodeスカラー値で指定したりできる`
と評価されます。
プリミティブの整理
char
型の導入に伴って、これに関係するプリミティブを導入する必要があります。
また、string
型をchar list
として扱うことができるようになることでプリミティブをライブラリ定義関数とすることができるようになります。
追加する必要があるもの
unicode-scalar-to-char : int -> char
:Unicodeスカラー値から文字を生成するchar-to-unicode-scalar : char -> int
:Unicodeスカラー値を取得する- 文字に関する情報を取得するプリミティブ
- 書記素クラスタ分割用プリミティブ
削除できるもの
string-unexplode : int list -> string
string-explode : string -> int list
string-same : sting -> string -> bool
string-sub : string -> int -> int -> string
string-length : string -> int
split-into-lines : string -> (int * string) list
おわりに
以上が文字列周りを整理して使いやすくするために必要そうな修正の提案でした。
string
型の記法の具体的な構文や書記素クラスタの扱いについてはまだ検討が必要な箇所もありますが、大まかな方針としては良いものになっていると思います。
ご意見お待ちしています。