format
(format destination control-string args...)
Common Lisp の format のサブセットで、インタプリタと両コンパイラで共有されるマクロとして実装されています。リテラルの control-string はコンパイル時に展開され、計算された制御文字列は実行時に描画されます(実行時の制御文字列を参照)。どちらでもディレクティブは同じです。destination が t の場合は princ/prin1/terpri 呼び出しに展開され、標準出力に書き出して nil を返します。destination が nil の場合は整形済み文字列を組み立てて返します(princ-to-string/prin1-to-string 呼び出しに展開され、内部の文字列連結で畳み込まれます)。それ以外の destination 式の場合は同じ方法で文字列を組み立てたうえで、その値を実行時に判別します — ストリームなら 1 回の write-string 呼び出しで書き込んで nil を返し(with-output-to-string の文字列ストリームまたはファイルストリーム)、t なら *standard-output* に書き、nil なら文字列を返します。この判定が実行時でなければならないのは、nil がストリームを表さないからです。nil は「文字列を返す」destination なので、自分の &optional stream 引数をそのまま渡す関数((defun render (x &optional stream) (format stream ...)) という Common Lisp の慣習)は、引数なしで呼ばれたときに文字列を返します。すべての引数は出力の前に左から右へ評価されます。
Hello WORLD, you are 42!
destination が nil の場合、結果は出力されずに文字列として返されます。
Directives
| ディレクティブ | 意味 |
|---|---|
~a, ~A | Aesthetic: 引数を princ のように出力します(文字列は引用符なし)。: を付けると nil は () として出力されます |
~s, ~S | Standard: 引数を prin1 のように出力します(読み込み可能。文字列は引用符付きで、中の " と \ はエスケープされます)。: を付けると nil は () として出力されます |
~w, ~W | Write: 引数を write のように出力します — プリンタ制御変数のもとでの prin1 です。プレフィックスパラメータは取りません。修飾子はプリンタが解釈しない変数を束縛するだけなので(~:W は *print-pretty*、~@W は *print-level*/*print-length* を nil に束縛)、3 つの綴りはいずれも同じテキストを出力します |
~d, ~D | 10進整数。: を付けると桁がカンマで区切られ、@ を付けると非負の値の前に + 記号が付きます |
~x, ~o, ~b | 16進 / 8進 / 2進整数(大文字の桁)。パラメータと修飾子は ~d と同じです |
~R | 基数: ~NR は整数を基数 N(2〜36)で出力します。基数パラメータがない場合は10進数の桁を出力します(英語の基数詞・序数詞出力は未実装) |
~c, ~C | 文字: write-char のようにグリフを出力します。@ を付けると #\ リーダ構文(prin1 相当)、: を付けると非表示文字は名前(Newline、Space など)で出力されます |
~f, ~F | 固定小数点形式の浮動小数点数。~,Df は小数点以下 D 桁を出力します(四捨五入)。@ を付けると先頭に + が付きます。完全なパラメータ形式は ~w,d,k,overflowchar,padchar F です |
~e, ~E | 指数(科学的)表記の浮動小数点数: [-]d.ddde[+/-]xx。~,De は小数点以下 D 桁を出力します(デフォルト 6、四捨五入)。@ を付けると先頭に + が付きます。完全なパラメータ形式は ~w,d,e,k,overflowchar,padchar,exponentchar E です(k は 1 のみ) |
~g, ~G | 一般浮動小数点形式: 絶対値が [0.1, 1e16)(およびゼロ)の場合は通常の浮動小数点表現、それ以外は ~e 形式で出力します |
~$ | 通貨形式: ~D$ は小数点以下 D 桁を出力します(デフォルト 2)。@ を付けると先頭に + が付きます |
~% | 改行(1つ、またはプレフィックスパラメータで指定された数) |
~& | フレッシュライン: 既に行頭にいない場合のみ改行します |
~~ | リテラルの ~ |
~(str~) | 処理済みの str の大文字小文字変換: 小文字化。~:( は各単語の先頭を大文字化、~@( は最初の単語だけ大文字化、~:@( は大文字化します |
~[str0~;str1~:;default~] | 条件選択: 引数(整数)で節を選択します。~:; はデフォルト節を導入します。~N[ / ~#[ はリテラル / 残りの引数の数で選択し、~:[false~;true~] は nil を判定し、~@[str~] は引数が非 nil の場合のみ str を処理します(判定した引数を再利用します) |
~{str~} | 繰り返し: リスト引数の要素に str を繰り返し適用します。~N{ は繰り返し回数を N に制限し、~:{ はサブリストのリストを、~@{ は残りの引数を、~:@{ は残りの各引数をサブリストとして繰り返します |
~? | 再帰 format: 制御文字列とその引数リストを消費し、ランタイムレンダラで描画します。~@? は内側の制御文字列の引数をリストではなく残りの引数から取ります |
~* | 引数ジャンプ: ~N* は N 個の引数をスキップし(デフォルト 1)、~N:* は N 個戻り、~N@* は引数 N へ移動します(デフォルト 0) |
ディレクティブはプレフィックスパラメータ(~ の後にカンマ区切りで記述)と :/@ 修飾子を受け付けます。パラメータは10進数、文字('c)、v(引数を1つ消費してその値を使う)、または #(残りの引数の数)のいずれかです。フィールドディレクティブ(~a/~s/~d/~x/~o/~b/~f/~e/~$)は先頭に最小幅パラメータを取ります。幅より短いテキストはパディングされます(パッド文字パラメータで指定 -- 'c リテラルまたは実行時の v -- デフォルトは空白)。~a/~s は右側をパディングします(@ を付けると左側)。数値は左側をパディングします。
Hello WORLD, you are 42 years old.
1,000,000 and +42
3.14 and 3.14
1.2345e+3 and 3.1416e+0
foo |00042|
"str" and str
Hello WORLD!
FF 100 101 10000
a #\b Newline
foo bar Foo Bar
one yes x=42
<1><2> (X,1)(Y,2) A B
1, 2, 3
1 1
Limitations
その他の出力先(フィルポインタ付き文字列)はサポートされていません。ループ脱出の ~^ はトップレベルと ~{ ... ~} / ~@{ ... ~} の本体内でサポートされます(結合イディオム "~{~a~^, ~}"。~:{ ... ~} の中では現在のサブリストの本体を終了します)が、~:^/~@^ とプレフィックスパラメータには対応していません。さらに以下の点に注意してください。
- 桁数指定のない
~f(および~gの固定小数点側)は自由形式の浮動小数点出力(最短ラウンドトリップ 10 進表現、すべてのバックエンドで同一)にフォールバックします。桁数指定付きの~fでは決して現れない指数表記になることがあるため、固定小数点のレイアウトが必要なら桁数を指定してください。~gはプレフィックスパラメータを受け付けません。 ~eは仮数部を整数演算から組み立てるため(どのバックエンドでも出力は同一になります)、桁数はリテラルでなければならず、実行時のvは使えません。スケーリングされた仮数部は 64 ビット演算で計算されるため、~,Deの精度はおよそD<= 17 桁に制限されます(どのバックエンドでも同一です)。デフォルト(~e、6桁)はその範囲に十分収まります。~eのスケールファクターは 1(デフォルト)のみで、~f/~eのオーバーフロー文字にはリテラルの幅指定が必要です。~%/~&/~~の繰り返し回数はリテラルまたは#でなければなりません(そこでは実行時のv回数はサポートされません)。~&は destination がtの場合は実際の出力カラムから改行を出すかどうかを判断しますが、destination がnilの場合と複合ディレクティブ(~(/~[/~{)の本体内では周囲のリテラルテキスト(静的な近似)から判断します。- 複合ディレクティブは自由にネストできます。
~[の条件節の中に別の~[や~{ ... ~}の反復を置けます。実行時に節を選択する~[の各節が消費する引数の数が異なる場合は、制御文字列の残りを節ごとに 1 回ずつ展開するため、各分岐は Common Lisp の引数ポインタと同じように自分自身の引数位置から続行します。渡された引数より多くを必要とする分岐は、実際に選択された場合にのみエラーを通知します。 formatは静的に展開されるため、~@[の節は判定した引数だけをちょうど消費しなければならず、~{ ... ~}の本体内では#と~@{は使えず、他の複合ディレクティブ(~(/~{)の内側にある引数消費数の異なる~[はサポートされません。~:dによる桁区切りと基数ディレクティブ~x/~o/~b/~rは、どのバックエンドでも任意の大きさの整数に対して正確です。~wはprin1として出力し、*print-escape*/*print-readably*を参照しません。そのため、これらを束縛してもprincには切り替わりません(write-to-stringと同じ制限です。write自身はこれらを解釈します)。
実行時の制御文字列
制御文字列が実行時の値になる場合 -- 計算された制御文字列、関数値 #'format を介した呼び出し(funcall/apply)、~? の内側の制御文字列、コンディションの format-control スロット -- は、静的に展開される代わりにランタイムレンダラで描画されます。レンダラは上の表と同じディレクティブを解釈するため、同じ制御文字列と引数であれば format にどの経路で渡しても(どのバックエンドでも)同じテキストになります。制御文字列がソースではなくデータであることから、次の2点が異なります。
- レンダラはエラーを通知しません。壊れた制御文字列(
"abc~")、未知のディレクティブ(~Q)、閉じられていない~{、足りない引数は、エラーではなくテキストとして描画されます(ディレクティブはそのまま、足りない引数はNIL)。リテラルの制御文字列であれば同じ問題は展開時に報告されます。診断はそこで出すべきものです。一方、実行時の制御文字列は報告対象のデータと一緒に届くことが多く、報告そのものが失敗してはなりません。 - カラム制御ディレクティブ
~t(~n,mT、~n@T)、複数形ディレクティブ~p、論理ブロック / 均等割り付けの~<...~>、関数呼び出しの~/name/はここでは利用できますが、リテラルの展開にはありません。リテラルの制御文字列でこれらを使うとこのレンダラにフォールバックするため、いずれも動作します。~tはそこまでに描画されたテキストからカラムを測ります。
~<...~> は均等割り付け、~<...~:> は論理ブロックで、どちらになるかは閉じディレクティブが決めます。セクションの規則は標準どおりで、均等割り付けの ~; で区切られた各セクションは順に引数を消費し、論理ブロックでは最初のセクションがプレフィックス、セクションが 3 つあるときは最後がサフィックスになります(どちらも引数を消費しません)。@ の付かないブロックは 1 つの引数(リスト)を受け取り、それが本体の引数リスト全体になります。実装されないのはレイアウトです。最小カラムまでの詰め物も、右マージンでの折り返しも行われず、条件付き改行のうち行を分けるのは強制の ~:@_ だけです(~_ / ~:_ / ~@_ と ~i は何もしません)。他を判断するにはストリームの現在のカラムが必要ですが、rontolisp のストリームはそれを持ちません。pprint-newline が :mandatory しか扱わないのと同じ理由です。
~/name/ は名前付き関数を (name stream object colon-p at-p) として呼び、書き出された内容を差し込みます。名前は find-symbol と同様に解決され、コロン 1 つと 2 つは同じ意味なので、~/mypkg:helper/ は内部シンボルにも届きます。
コンパイル出力が ~/name/ を含むのは、コンパイラがそのディレクティブを見つけられた場合だけです。 制御文字列から実行時に関数を解決するということは、プログラム中のどの関数にも名前で到達できるということであり、それはまさにコンパイラが未使用コードを削除できなくなる条件です。そこでコンパイラは、プログラム中のいずれかの文字列リテラルが ~/name/ ディレクティブを綴っているときにだけ、レンダラのその部分を含めます(場所は問いません。呼び出し箇所の制御文字列でも、変数に束縛された制御文字列でも、取り込まれたライブラリの中でもかまいません)。通常の用法はこれで全て賄えます。ディレクティブを綴らない断片から実行時に組み立てられた制御文字列は、描画の代わりに理由を示すエラーを通知します。無条件に使えるようにするには --dynamic でコンパイルしてください。インタプリタでは常に利用できます。
基数パラメータのない ~r は10進数の桁を出力します。英語の基数詞・序数詞は実装されていません。
他のマクロと同様に、format はコンパイル出力に埋め込まれた eval ランタイムでは認識されません(コンパイルされた eval の制限を参照)。