Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@momeemt

Nimの文字列フォーマット strformat

はじめに

こんにちは。高校2年の樅山です。
Nim Advent Calendar 2020 の6日目が空いていたので、後から執筆して公開しました。
同時に、2020/11 から始まった、ものづくりをする高校生のための新しいグループ、「Palettte」が主催する Palettte Advent Calendar 2020 の10日目の記事でもあります。

他の標準ライブラリを調べる👇

strformat

strformat は、Pythonの文字列フォーマット(f)に触発されて開発されたNimの標準ライブラリの一つです。

文字列フォーマットとはある書式を設定した文字列のことで、よく使われる機能の一つとして、変数展開があります。Nimの式の文字列表現や、ある文字列を加工するのに使われます。

importする
import strformat

文字列フォーマットを使う

fmtマクロを呼び出すことで、文字列フォーマットを利用することができます。

fmtマクロ
import strformat

const cat = "nyaan"
echo fmt"cat: {cat}"

fmtマクロに、fmt"{expr}"のようにNimの有効な式を {} で囲んだ文字列を含む文字列を渡すと、式を展開して文字列表現に直します。
上のコードでは、cat: nyaan が出力されます。

raw 文字列リテラルとしての解釈

fmtマクロでは、raw 文字列リテラルとして解釈されます。
そもそも raw 文字列リテラルとは、特別な文字による解釈を行わない文字列リテラルのことです。通常の文字列(string)では、\nは改行コードとして扱われます。
しかし、raw 文字列リテラルでは \ はそのままバックスラッシュとして扱われるため、改行コードにはなりません。
つまり、

raw文字列リテラルとしての解釈
import strformat

let name = "momeemt"
echo fmt"{name}\n" == "momeemt\\n"

上のコードでは、trueが出力されます。
ここから、fmtマクロは、raw 文字列リテラルとして解釈していることが分かります。

untyped引数は展開できない

文字列フォーマットでは、様々な式を評価することができますが、untyped引数を評価することはできません。その理由は、処理順序にあります。

untyped引数は展開できない
template echoName(name: untyped): untyped =
  echo "name is: ", name
  echo fmt"--- {name} ---"

let momiyama = "momeemt"
myTemplate(momiyama)
  1. echoNameテンプレートがuntyped型の引数、nameを受け取る
  2. 引数 name が、echoNameテンプレート内の識別子 namename の実体(ここでは "momeemt")に置換していく
  3. ただし、文字列フォーマット内の {name} はまだ文字列リテラルとして解釈されるために、置換されない
  4. その後、fmt{name} を置換しようとするが、識別子 name は既に処理済みであるため例外を吐く

この動作を回避するためには、あらかじめ inject プラグマを用いて引数の実体をコピーしておく必要があります。

正しく展開される
template echoName(name: untyped): untyped =
  block:
    let name2 {.inject.} = name
    echo "name is: ", name2
    echo fmt"--- {name2} ---"

let momiyama = "momeemt"
myTemplate(momiyama)

こうすることで、fmt が処理されるまで引数の実体を持ち続けることができ、正しく展開されます。

&演算子

fmtマクロは、実は&マクロのエイリアスです。
基本的には fmt と同じように使えますが、& は渡された文字列を raw 文字列リテラルとして解釈しません。
つまり、

&はraw文字列リテラルとして解釈しない
import strformat

let name = "momeemt"
echo &"{name}\n" == "momeemt\n"

上のコードでは、trueが出力されます。
ここから、&マクロは、stringとして解釈していることが分かります。

開始・終了タグをカスタマイズする

{expr}と書くことで式を評価できることは分かりましたが、フォーマット文字列を使いたい上に、{}という文字でカッコを表現したい場合にはどうすれば良いのでしょうか。
式が含まれていなければ、{}は上手い具合に無視されるかと言えば、そうではありません。

パースに失敗する
import strformat

let name = "momeemt"
echo fmt"{} {name}"

上のコードでは、実行すると

stack trace: (most recent call last)
/playground/nim/lib/pure/strformat.nim(652, 53) fmt
/playground/nim/lib/pure/strformat.nim(618, 18) strformatImpl
/usercode/in.nim(4, 9) Error: could not parse ``.
/playground/nim/lib/core/macros.nim(549, 29) Error: expected expression, but got multiple statements

例外を吐き出してしまいました。
どうしても、式評価のための開始・終了タグが{}では不都合な場合には、それぞれを渡すことでカスタマイズすることができます。

カスタム開始・終了タグ
import strformat

let name = "momeemt"
echo fmt("{} <name>", '<', '>')

上のコードのように、fmt fmtStr: string openChar: char closeChar: char と引数を渡すと、開始タグは openCharに、終了タグは closeCharに変更されます。

標準フォーマット指定子を使う

文字列フォーマットには、標準フォーマット指定子という強力な機能がサポートされています。
何らかのデータを標準出力したい場合などに、整形のためのプロシージャをいちいち用意しなくても、標準フォーマット指定子を上手く使えば、求めているような書式に自動でフォーマットすることができます。

デバッグのためのフォーマット

何らかの値を確認するために、echo デバッグすることは往々にしてあると思います。
任意の式の後に、= を付けた、 { expr = } という指定子を使えば、式と評価された結果を同時に出力できます。

=フォーマット
import strformat

echo fmt"{ 3 * 4 + 8 div 2 = }"

# output
# 3 * 4 + 8 div 2 = 16

上のコードのように、デバッグに最適な書式です。
この書式は、スペースに依存して出力を行います。

左揃え / 中央揃え / 右揃え

スペースを直打ちしなくとも、左揃え / 中央揃え / 右揃えを標準フォーマット指定子で簡単に実現できます。

左揃えフォーマット
import strformat

echo fmt"""{"Nim":<12}"""
echo fmt"""{"C":<12}"""
echo fmt"""{"C++":<12}"""
echo fmt"""{"Objective-C":<12}"""
echo fmt"""{"JavaScript":<12}"""

# Nim         
# C           
# C++         
# Objective-C 
# JavaScript  

※ コピーしていただければ分かりますが、12文字左揃えになっています

中央揃えフォーマット
import strformat

echo fmt"""{"Nim":^12}"""
echo fmt"""{"C":^12}"""
echo fmt"""{"C++":^12}"""
echo fmt"""{"Objective-C":^12}"""
echo fmt"""{"JavaScript":^12}"""

#     Nim     
#      C      
#     C++     
# Objective-C 
#  JavaScript 
右揃えフォーマット
import strformat

echo fmt"""{"Nim":>12}"""
echo fmt"""{"C":>12}"""
echo fmt"""{"C++":>12}"""
echo fmt"""{"Objective-C":>12}"""
echo fmt"""{"JavaScript":>12}"""

#         Nim
#           C
#         C++
# Objective-C
#  JavaScript

この指定子は、文字列の文字数を含めて◯文字で左揃え・中央揃え・右揃えにフォーマットします。
お分かりかと思いますが、< が左揃え、^ が中央揃え、> が右揃えです。

符号フォーマット

数値(整数、浮動小数点数にparse 可能な値)である文字列は、符号について書式を設定することができます。
ただし、指定しなかった場合はデフォルトで - が指定されます。

指定子 変換結果
+ 正負に関わらず、符号を使った表記に変換します。
- 負の数の場合、符号(-)を使った表記に変換します。
(スペース) 正の数の場合、先頭にスペースを含んだ表記に変換します。

進数変換

数値(整数、浮動小数点数にparse 可能な値)である文字列は、# 指定子で進数変換することができます。
ただし、指定しなかった場合はデフォルトで d が指定されます。

指定子 変換結果
#b 2進数にフォーマットします。
#d 10進数にフォーマットします。
#o 8進数にフォーマットします。
#x 16進数にフォーマットします。ただし、アルファベットは小文字で出力されます。
#X 16進数にフォーマットします。ただし、アルファベットは大文字で出力されます。

浮動小数点数フォーマット

浮動小数点数である文字列は、指定子を用いてフォーマットすることができます。
ただし、指定しなかった場合はデフォルトで、一般的な数値は固定小数点数、大きすぎる数値は指数表記でフォーマットします。アルファベットは小文字で出力し、最低でも小数点一桁までを表示します。

指定子 変換結果
e 指数表記にフォーマットします。ただし、アルファベットは小文字で出力されます。
E 指数表記にフォーマットします。ただし、アルファベットは大文字で出力されます。
f 固定小数点数にフォーマットします。ただし、アルファベットは小文字で出力されます。
F 固定小数点数にフォーマットします。ただし、アルファベットは大文字で出力されます。
g 一般的な数値は固定小数点数で、大きすぎる数値は指数表記でフォーマットします。ただし、アルファベットは小文字で出力されます。
G 一般的な数値は固定小数点数で、大きすぎる数値は指数表記でフォーマットします。ただし、アルファベットは大文字で出力されます。

StandardFormatSpecifier 型

StandardFormatSpecifier 型は、標準フォーマット指定子を管理する型です。

StandardFormatSpecifier型
StandardFormatSpecifier = object
  fill*, align*: char
  sign*: char
  alternateForm*: bool 
  padWithZero*: bool 
  minimumWidth*, precision*: int
  typ*: char
  endPosition*: int
プロパティ 意味
fill 書式を整える塗り潰しの設定
align 左右中央揃えの設定
alternateForm 進数変換を行うかどうか
padWithZero スペースではなくゼロで埋めるかどうか
minimumWidth 最小文字数
precision 浮動小数点数フォーマットの精度
typ 浮動小数点数フォーマット、符号フォーマット、進数変換の設定
endPosition 標準フォーマット指定子の終了位置

fmtを使わずにフォーマットを行う

標準フォーマット指定子だけでは飽き足らず、自分でフォーマットを行いたい場合には、strformatモジュールで提供されているプロシージャを用いると便利です。

文字列を整列させる

文字列を整列させる場合には、alignStringプロシージャを使うと便利です。

alignString
proc alignString(s: string; minimumWidth: int; align = '\x00'; fill = ' '): string {.
    raises: [], tags: [].}
文字列を整列させる
import strformat

echo "momeemt".alignString(10, '<')
echo "momeemt".alignString(10, '^')
echo "momeemt".alignString(10, '>')
echo "momeemt".alignString(10, '<', '@')

# momeemt   
#  momeemt  
#    momeemt
# momeemt@@@

第一引数に加工元の文字列を、第二引数に左、中央、右揃えの設定(<, ^, >)を、第三引数に塗り潰しの文字を渡します。
揃えの設定と、塗り潰しの文字に関してはデフォルトで値が代入されるので省略しても構いません。

文字列をStandardFormatSpecifier型のオブジェクトにパースする

parseStandardFormatSpecifier
proc parseStandardFormatSpecifier(s: string; start = 0;
                                  ignoreUnknownSuffix = false): StandardFormatSpecifier {.
    raises: [ValueError], tags: [].}

カスタムフォーマット指定子を作成する場合に必要です。

システム内部のプロシージャ

formatValue プロシージャは、fmtマクロの動作に必要なプロシージャで、直接呼び出す意味はありません。

おわりに

strformatを上手に利用することで、文字列をより柔軟に扱えるようになります。
ぜひ、実際に文字列フォーマットを活用したり、ユースケースに合わせてカスタムフォーマット指定子を作成して、開発効率を上げていきましょう。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
momeemt
こんにちは。高校3年生の樅山です。 Nimの記事を書いています。 標準ライブラリの解説をするのが好きです。
nim-in-japan
Nim言語の日本コミュニティです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?