ソースコードフィルタ

ソースコードフィルタ (Source Code Filter - 以下、SCF) は、解析前にメモリ内に存在する出力ストリームへ入力された文字ストリームを加工・変換します。フィルタはテンプレート処理システム、またはプリプロセッサとして使えます。

フィルタを使うにはソースコードで #? 記法を使います:

#? stdtmpl(subsChar = '$', metaChar = '#')
#proc generateXML(name, age: string): string =
#  result = ""
<xml>
  <name>$name</name>
  <age>$age</age>
</xml>

前述の用例の通り、フィルタへの引数渡しは通常のプロシージャと同様に名前のある変数、また引数の位置を指定することで呼び出せます。指定できるパラメータはフィルタごとに異なります。version 0.12.0 以前の言語仕様では #? ではなく #! が使われていました。

ヒント: With --hint[codeBegin]:on--verbosity:2 (またはそれ以降) でコンパイル、あるいは nim check のより中に、 Nim は各種フィルターアプリケーションで処理を終えたコードのリストを作成します。

用法

まず、フィルタの指定として SCF のコードをファイルごとに一行目へ挿入します。備考: SCF ファイルの拡張子はお好みで命名できるものの、命名規則上の理由から拡張子 .nimf をお使いください (以前は .tmpl でしたが、あまりにも濫用されているが故に、 GitHub では Nim のソースファイルとして認識できません)。

前述のコードで generateXML を使う場合は main.nim で SCF ファイル xmlGen.nimf を呼び出します:

include "xmlGen.nimf"

echo generateXML("John Smith","42")

パイプ演算子

パイプ演算子 | でフィルタを組み合わせて使えます。

#? strip(startswith="<") | stdtmpl
#proc generateXML(name, age: string): string =
#  result = ""
<xml>
  <name>$name</name>
  <age>$age</age>
</xml>

フィルタの種類

置換フィルタ

置換フィルタは各行の部分文字列を置換します。

パラメータとデフォルト値:

sub: string = ""
検索対象の部分文字列
by: string = ""
部分文字列の置換に使う文字列

除去フィルタ

除去フィルタは各行の先頭と末尾にあるホワイトスペースを削除します。

パラメータとデフォルト値:

startswith: string = ""
startswith で始まる行のみ削除します (先頭のホワイトスペースは無視します)。空文字列を指定すると、すべての行を削除します。
leading: bool = true
先頭にあるホワイトスペースを削除します
trailing: bool = true
末尾にあるホワイトスペースを削除します

StdTmpl フィルタ

stdtmpl フィルターは Nim のシンプルなテンプレートエンジンの機能です。フィルタは行指向のパーサを使います: Nim コードの行には接頭辞として メタ文字 (デフォルト: #) が存在し、それ以外の行は一語一句そのままの状態で扱われます。この理由として、制御フローのステートメントでは境界定義子 (delimiter) である end X が必要になるため、テンプレートエンジンにおいてインデントベースの解析処理は不向きだからです。

パラメータとデフォルト値:

metaChar: char = '#'
Nim コードが存在する行の接頭辞
subsChar: char = '$'
テンプレート行に存在する Nim 表現式の接頭辞
conc: string = " & "
連結命令
emit: string = "result.add"
文字列リテラルを生成する命令
toString: string = "$"
式ごとに適用される命令

用例:

#?stdtmpl | standard
#proc generateHTMLPage(title, currentTab, content: string,
#                      tabs: openArray[string]): string =
#  result = ""
<head><title>$title</title></head>
<body>
  <div id="menu">
    <ul>
  #for tab in items(tabs):
    #if currentTab == tab:
    <li><a id="selected"
    #else:
    <li><a
    #end if
    href="${tab}.html">$tab</a></li>
  #end for
    </ul>
  </div>
  <div id="content">
    $content
    A dollar: $$.
  </div>
</body>

フィルタの変換結果:

proc generateHTMLPage(title, currentTab, content: string,
                      tabs: openArray[string]): string =
  result = ""
  result.add("<head><title>" & $(title) & "</title></head>\n" &
    "<body>\n" &
    "  <div id=\"menu\">\n" &
    "    <ul>\n")
  for tab in items(tabs):
    if currentTab == tab:
      result.add("    <li><a id=\"selected\" \n")
    else:
      result.add("    <li><a\n")
    #end
    result.add("    href=\"" & $(tab) & ".html\">" & $(tab) & "</a></li>\n")
  #end
  result.add("    </ul>\n" &
    "  </div>\n" &
    "  <div id=\"content\">\n" &
    "    " & $(content) & "\n" &
    "    A dollar: $.\n" &
    "  </div>\n" &
    "</body>\n")

メタ文字 (先頭のホワイトスペースは無視される) で開始されない行はすべて文字列リテラルへ変換後に result へ追加されます。

代理文字は文字列リテラル内の Nim 表現式 e で制定します。$ にはデフォルトで toString 命令が定義されています。これを用いることで e は文字列へ変換されます。強い型検査では、 toString へ空文字列を設定してください。この PEG パターンと e は必ず一致します:

e <- [a-zA-Z\128-\255][a-zA-Z0-9\128-\255_.]* / '{' x '}'
x <- '{' x+ '}' / [^}]*

代理文字を生成するには対象の単体文字を二連にします: $$$ を生成します。

テンプレートエンジンはかなり柔軟です。テンプレートコードをファイルへ出力するプロシージャを作成するのは容易です:

#?stdtmpl(emit="f.write") | standard
#proc writeHTMLPage(f: File, title, currentTab, content: string,
#                   tabs: openArray[string]) =
<head><title>$title</title></head>
<body>
  <div id="menu">
    <ul>
  #for tab in items(tabs):
    #if currentTab == tab:
    <li><a id="selected"
    #else:
    <li><a
    #end if
    href="${tab}.html" title = "$title - $tab">$tab</a></li>
  #end for
    </ul>
  </div>
  <div id="content">
    $content
    A dollar: $$.
  </div>
</body>