【Nim】個人的逆引きリファレンス
Nimの勉強を兼ねて逆引きリファレンスとしてまとめてみようと思います。随時更新。(最終更新日:2022/4/10)
この記事は「Nim Advent Calendar 2017」の記事として登録させてもらっています。
注意:
- 使用しているNimのバージョンは1.6.0です
- 間違っているかもしれません
- サンプルコードはテストしていないものが多いです
- 先に公式マニュアルなどを一通り読むことをおすすめします
- Nimツール
- 基本文法
- コメントを記述する
- 変数を定義する
- 定数を定義する
- 文字列リテラルを利用する
- 数値リテラルを利用する
- プロシージャを呼び出す
- プロシージャを定義する
- 式の場所で文を記述する
- if文を利用する
- case文を利用する
- for文を利用する
- while文を利用する
- block文を利用する
- break文を利用する
- when文を利用する
- イテレータを定義する
- プロシージャル型を利用する
- enum型を定義する
- enum値を数値に変換する
- enum値を専用のスコープ内に定義する
- enum値の文字列表現を指定する
- Subrange型を利用する
- 型に別名を付ける
- 既存の型から独自の型を作る
- 固定長配列(array型)を利用する
- 動的配列(seq型)を利用する
- 可変個引数を利用する
- タプルを利用する
- Object型を利用する
- Set型
- 参照を利用する
- 無名プロシージャを利用する
- クロージャを利用する
- ジェネリクスを利用する
- テンプレートを利用する
- マクロを利用する
- モジュールを利用する
- 例外を利用する
- Option型を利用する
- マクロ
- デバッグ
- その他
- ファイル
- ファイルの存在チェック : os.fileExists( "path" )
- ファイル読み込み : io.readFile( "path" )
- ファイル書き込み : io.writeFile( "path", fileContent )
- ファイル削除 : os.remove( "path" )
- ファイルコピー : os.copyFile( src_path, dest_path )
- ファイル名変更 : os.moveFile( old_filename, new_filename )
- ファイル移動 : os.moveFile( src_path, dest_path )
- ファイル更新日取得 : os.getLastModificationTime( "path" )
- ファイル更新日変更 : os.setLastModificationTime( "path", time )
- 指定のディレクトリ内のファイルリストを取得
- 指定のディレクトリ内のファイルリストを取得(再帰的) : collect( for i in os.walkDirRec( "path" ): i )
- ディレクトリ
- パス操作
- パスをつなげる : os.joinPath( "path1", "path2" )
- ファイルパスから絶対パスを取得する : os.absolutePath( "path" )
- ファイルパスからファイル名(拡張子付き)を取得する : os.extractFilename( "path" )
- ファイルパスからファイル名(拡張子なし)を取得する : os.splitFile( "path" ).name
- ファイルパスからディレクトリ名を取得する : "path".splitFile().dir.splitFile().name
- ファイルパスからディレクトリパスを取得する : os.splitFile( "path" ).dir
- ファイルパスから拡張子を取得する : os.splitFile( "path" ).ext
- ファイルパスがディレクトリを指しているかどうかを取得する : os.dirExists( "path" )
Nimツール
Nimを普通に実行する : nim c -r Nimソースファイルパス
(コマンドライン) # 普通に実行 nim c -r Nimソースファイルパス # 余分な情報を出力しない場合 nim c -r -hints:off Nimソースファイルパス # プログラムに引数を渡したい場合は、最後に追加する nim c -r -hints:off Nimソースファイルパス 引数1 引数2...
ドキュメント :
Compiler Usage
基本文法
コメントを記述する
# コメント ## ドキュメント用コメント #[ 複数行コメント #[ ネスト可能 ]# ]#
ドキュメント :
Comments
Multiline comments
変数を定義する
var 識別子: 型名 var 識別子: 型名 = 初期値 var 識別子 = 初期値
var hoge: int # 変数定義 var hoge2: int = 10 # 変数定義(初期値指定) var hoge3 = 10 # 初期値指定する場合は型の記述を省略可能 var a, b, c: int # まとめて定義 var # varセクションでまとめて定義 x, y, z: int name: string # var ではなく let を使うことで変更不可の変数を定義することができる let l_hoge = 1 # l_hoge = 2 # コンパイルエラーになる
ドキュメント :
Var statement
let statement
定数を定義する
const HOGE = "hoge" var hoge2 = "hoge2" const HOGE2 = hoge2 # 定数なのでコンパイル時に値が確定しないものは設定できない
ドキュメント :
Const section
文字列リテラルを利用する
# 文字列リテラル(String literal) echo( "abc" ) # 出力:abc # raw文字列リテラル(Raw string literal) # 文字がエスケープされない echo( r"newline character is '\n'" ) # 出力:newline character is '\n' # 文字列リテラル(Triple quoted string literal) # 文字がエスケープされない。改行ができる。 echo( """a b c """ ) # 出力: # a # b # c # pythonとは違い''は文字列リテラルとして使えない # echo( 'abc' )
ドキュメント :
String literals
Triple quoted string literals
Raw string literals
数値リテラルを利用する
import typetraits # nameプロシージャのため echo( 123 ) # 123 # 途中の_は無視される echo( 123_456 ) # 123456 # 浮動小数点数 echo( 123.0 ) # 123.0 echo( 123e1 ) # 1230.0 echo( 123.0e1 ) # 1230.0 # 16進数 echo( 0xFF ) # 255 # 2進数 echo( 0b0010 ) # 2 # 8進数 echo( 0o0010 ) # 8 # サフィックスで型を指定できる echo typetraits.name( type( 123 ) ) # int echo typetraits.name( type( 123'i8 ) ) # int8 echo typetraits.name( type( 123'f ) ) # float32 echo typetraits.name( type( 0xFF'f ) ) # float32
ドキュメント :
Numerical constants
プロシージャを呼び出す
echo( "aaa" ) # 普通にプロシージャ呼び出し # コマンド呼び出し構文(Command invocation syntax) echo 1 # ()を省略できる echo 1, 2 # ()を省略して複数引数を渡すこともできる # メソッド呼び出し構文(Method call syntax) 1.echo() # echo(1)と等価。 1.echo( 2 ) # echo(1,2)と等価。 1.echo # コマンド呼び出し構文との組み合わせ。echo(1)と等価。 1.echo 2 # コマンド呼び出し構文との組み合わせ。echo(1,2)と等価。 # 名前付き引数 proc test_proc( a:int, b:string ) = echo a, b test_proc( 1, "hoge" ) test_proc( 1, b="hoge" ) # 名前付き引数 # ジェネリクスの場合の注意 type A = object proc test_template[T]( value: A ) = echo T.name var a = A() # a.test_template[int]() # (a.test_template)[int]と解釈されてしまうのでコンパイルエラーになる a.test_template[:int]() # [int]ではなく[:int]とすることで回避できる
ドキュメント :
Procedures
Command invocation syntax
Method call syntax
プロシージャを定義する
proc 識別子(仮引数名:型名, 仮引数名:型名...): 戻り値型 = プロシージャ本体
# 普通に定義 proc myProc1(name: string): bool = return name == "aaa" proc myProc2(name: string): bool = return name == "aaa" # 1行なら改行不要 proc myProc3(name: string): void = echo( name ) # 戻り値なし proc myProc4(name: string) = echo( name ) # 戻り値なしなら戻り値型指定は省略できる proc myProc5(name: string): bool = name == "aaa" # 最後の式は暗黙でreturnされる proc myProc6(name: string = "aiueo") = echo( name ) # デフォルト引数を設定可能 proc myProc7(name = "aiueo") = echo( name ) # デフォルト引数を設定した場合、型指定は省略できる proc myProc8(name: string): bool # 宣言のみ myProc8() # この時点でプロシージャ定義がされていなくても宣言があれば呼び出せる proc myProc8(name: string): bool = return true # 宣言したらプロシージャ定義は必要(ないとコンパイルエラー) proc myProc9(name: string): bool = result = true # 暗黙のresult変数が利用できる(暗黙にreturnされる) echo( name )
プロシージャの他の特徴としては以下のものがある
- 仮引数は変更不可
- プロシージャ呼び出し側に戻り値を必ず受け取らせるかどうかを設定可能(デフォルトでは有効) :
{.discardable.}
- オーバーロード可能
- クロージャを作成可能(プロシージャ内部で定義されたプロシージャはクロージャとなる)
- 無名プロシージャを作成可能
- 動的ディスパッチに対応したプロシージャ(C++でいう仮想関数)を定義することが可能
- イテレータを定義可能
- インライン化指定可能 :
{.inline.}
- 各種呼び出し規約を指定可能
- モジュール外部に公開するかどうかを設定可能(Export marker)
- 引数を参照で受け取ることが可能(ここでいう参照とはC++的な参照)
- 戻り値を参照で返すことが可能(ここでいう参照とはC++的な参照)
- ジェネリクスプロシージャを定義可能(C++でいうテンプレート関数)
- その他特殊なプロシージャを定義可能
- 暗黙の型変換用プロシージャ
- 各種オペレーター
- ユーザー定義オペレーター
ドキュメント :
Procedures
式の場所で文を記述する
(文;文;式)
最後の式が評価結果になる。
const fac4 = (var x = 1; for i in 1..4: x *= i; x)
ドキュメント :
Statements and indentation
if文を利用する
if name == "abc": echo( "ABC" ) elif name == "defg": discard # Pythonのpass文のような用途としてもdiscard文が使える else: echo "???" # if文内は個別のスコープになる if (var hoge = true; hoge): echo( hoge ) else: # echo( hoge ) # hogeはスコープ外 discard # echo( hoge ) # hogeはスコープ外
ドキュメント :
If statement
case文を利用する
C言語のSwitch文のようなもの。
case 式 of 式: 文 of 式: 文 else: 文
case name of "abc": echo( "ABC" ) of "def", "ghi": # ,で複数条件を指定できる discard # Pythonのpass文のような用途としてもdiscard文が使える else: discard # case以降でインデントが許されている(インデントする場合はcaseの後に : が必要) case name: of "abc": echo( "ABC" ) of "def", "ghi": # ,で複数条件を指定できる discard # Pythonのpass文のような用途としてもdiscard文が使える else: discard
ドキュメント :
Case statement
for文を利用する
for 要素を受け取る変数 in イテレータ: 文 for インデックスを受け取る変数, 要素を受け取る変数 in イテレータ: 文
for i in countup(1, 10): if false: continue # continue文が使える if false: break # break文も使える echo i for i in 1..10: # 1..10はcountup(1,10)と同じ echo i for i in 1..<11: echo i type MyEnum = enum A, B, C for i in MyEnum: # enum型に含まれるenum値を走査できる echo i for index, value in ["a","i","u","e","o"]: # インデックス付きで走査も可能(できない型もある) echo "Index=", index, " Value=", value
for文に渡したものがイテレータでない場合、itemsイテレータ(変数が1個の場合)かpairsイテレータ(変数が2個の場合)が暗黙に呼ばれる。このとき、itemsイテレータもしくはpairsイテレータが定義(オーバーロード)されていない場合、コンパイルエラーになる。この仕組により、array型やseq型をfor文で利用できるようになっている(array型やseq型のitems/pairsイテレータが標準で実装されている)。
標準で実装(オーバーロード)されているitems/pairsイテレータ
- openArray[T]
- array[IX, T]
- set[T]
- cstring
- typedesc[enum]
- Slice[T]
- seq[T]
- string
標準で実装されているイテレータ(詳細はIterators(ライブラリリファレンス)を参照)
- countdown
- countup
- `..`
- `||`
- items
- mitems
- pairs
- mpairs
- fields
- fieldPairs
- lines
- `..<`
for i in 0..5:
のように書けるのは`..`演算子がイテレータになっているから。
ドキュメント :
Iterators and the for statement
For statement(チュートリアル)
Iterators(ライブラリリファレンス)
while文を利用する
var i = 1 while i <= 10: if false: continue # continue文が使える if false: break # break文も使える echo i inc( i )
ドキュメント :
While statement
inc
block文を利用する
block: var x = "hi" #[ 変数xはスコープ外なのでアクセスできない echo x # エラー ]# block myBlock: # ブロックに名前を付けることもできる discard
ドキュメント :
Block statement
break文を利用する
break文は for文, while文, block文 で利用できる
for i in 0..5: if i == 3: break # ブロック名を指定して抜け出すこともできる block myBlock: block myBlock2: if true: break # myBlock2から抜け出す else: break myBlock # myBlockから抜け出す
ドキュメント :
Break statement
when文を利用する
C言語における、#if
に近い機能。when文はコンパイル段階で条件分岐が行われる。そのため条件式は定数式しか受け付けない。また、ブロックスコープは発生しない。
when true: echo "aaa" var x = 10 elif true: apple = 10 # ここは意味解析が行われない(構文解析は行われるので変なこと書くとコンパイルエラーになる) else: discard echo x # ブロックスコープが発生しないのでxにアクセスできる
ドキュメント :
When statement
イテレータを定義する
イテレータについて
- イテレータはプロシージャの亜種
- イテレータは処理を中断(yield)、再開させることができる
- イテレータではyield文を使用することができる
- イテレータではreturn文やresult変数は使用できない
- for文はイテレータを繰り返し呼び出すことでループを行う
- イテレータはinlineとclosureの2種類がある
- イテレータはデフォルトではinlineタイプ
- inlineイテレータはfor文でしか使うことができない
- closureイテレータはfor文以外にも変数として扱うことが可能
- イテレータはプロシージャと別の名前空間になっているので、それぞれ同名のものを定義できる
- 標準ライブラリのstrutils.splitはこれを利用して、for文ではイテレータのsplit、その他ではプロシージャのsplitが呼ばれるようになっている
iterator countup(a, b: int): int = var res = a while res <= b: yield res inc(res) for i in countup(1, 10): echo i
ドキュメント :
Iterators and the for statement
Iterators(チュートリアル)
プロシージャル型を利用する
プロシージャル型はプロシージャへの参照のようなもの。プロシージャル型の変数を使うことでC言語の関数ポインタのような処理が可能になる。
proc test2(x:int):int = echo "test2:", x x # プロシージャ名単体はプロシージャル型として評価される echo( name( type( test2 ) ) ) # 出力:proc (x: int): int{.gcsafe, locks: 0.} var p2: proc(x:int):int = test2 # プロシージャル型を変数へ代入 var p3 = test2 # 型推論が働くので型を明示的に指定する必要はない assert p2(77) == 77 # プロシージャのように呼び出せる
ドキュメント :
Procedural type
enum型を定義する
type 識別子 = enum 識別子, ...
type # enum型を定義 MyEnum = enum A, B, C, D, # 改行を省略することもできる type MyEnum2 = enum A, B, C, D, # 値(ordinal value)を指定することもできる type MyEnum3 = enum one = 1, two, # 自動で+1されて2になる five = 5 # 順番を飛ばした値を指定することもできるが、ordinal typeではなくなり、incなどのプロシージャが使えなくなる # enum型の変数を定義 var enumValue: MyEnum # enum値は文字列化可能 echo enumValue # A
ドキュメント :
Enumeration types
enum値を数値に変換する
ord
プロシージャを使う
type MyEnum = enum A, B, C, D, var enumValue: MyEnum echo ord( enumValue ) # 0 echo ord( B ) # 1
ドキュメント :
Enumeration types
ord
enum値を専用のスコープ内に定義する
type MyEnum {.pure.} = enum A, B, C, D, # echo A # Aではアクセスできない echo MyEnum.A
ドキュメント :
Enumeration types
enum値の文字列表現を指定する
文字列を指定すると、文字列表現の際にその文字列が使用される。また、タプルで指定することで値(ordinal value)と文字列表現の両方を指定出来る。
type MyEnum = enum A = "A team", B = "B team", C = "C team", D = (8, "D team"), echo A # A team echo ord( A ) # 0 echo D # D team echo ord( D ) # 8
ドキュメント :
Enumeration types
Subrange型を利用する
Subrange型はOrdinal型の範囲を制限した型。
- 元となったOrdinal型をBase型と呼ぶ
- Subrange型のサイズはBase型と同じになる
- Subrange型への代入はコンパイル時または実行時にチェックが入る
- Base型からSubrange型への代入、またはその逆は許可されている
- ビット演算の
and
は一方の項が定数の場合、Subrange型に変換される(例:'x and 3'の評価結果は'range[0..3]'型になる) - Subrange型を作るには
range
型を使う
var hoge: range[0..5] hoge = 1 # hoge = 6 # 値の範囲外なのでエラー
ドキュメント :
Subrange types
型に別名を付ける
type MyInt = int MyInt2 = int var mi: MyInt var mi2: MyInt2 mi = mi2 # 型名は違うけど、実際の型はintなので代入できる
ドキュメント :
Type sections
既存の型から独自の型を作る
type MyInt = distinct int MyInt2 = distinct int var mi: MyInt var mi2: MyInt2 # mi = mi2 # 別の型なので代入できない
ドキュメント :
Distinct type
固定長配列(array型)を利用する
(array型) array[要素数,要素型] array[インデックス範囲,要素型] (Array型のオブジェクトを作成(Arrayコンストラクタ)) [要素1, 要素2, ...]
# 5要素のstringの配列を作成 var x : array[5, string] # Arrayコンストラクタ[]で作成した値を代入 x = ["a","b","c","d","e"] # for文で走査 for value in x: echo value # index付きで走査 for index, value in x: echo "Index=", index, " Value=", value # その他のArray作成例 var x2 : array[1..5, string] # 範囲指定で作成(0オリジンでない配列を作成可能) type MyEnum = enum A, B, C var x3 : array[MyEnum, MyEnum] # 範囲指定としてenum型を指定 # これらはx3と同じ範囲指定をしたことになる(同じ型として扱われる) var x4 : array[low(MyEnum)..high(MyEnum), MyEnum] var x5 : array[ord(high(MyEnum))+1, MyEnum] var x6 : array[0..2, MyEnum] var x7 : array[3, MyEnum]
標準で定義されているプロシージャ
- high : 最大のインデックスを取得
- low : 最小のインデックスを取得
- len : 要素数を取得
- min : 全要素の中から最小のものを取得
- max : 全要素の中から最大のものを取得
- `==` : 比較
- `@` : seq型へ変換したものを取得
- contains : 特定の値が要素に含まれるかどうかを取得
- `` : 要素を取得
- `=` : 要素へ代入
- items : for文走査用
- mitems : for文走査用
- pairs : for文走査用
- mpairs : for文走査用
- find : 特定の値の要素のインデックスを取得。存在しなかった場合は-1
ドキュメント :
Array and sequence types
【おまけ】Arrayのジェネリクスパラメータは何を受け付けるのか
Arrayは以下のようにジェネリクスとして定義されている(system.nim)
array[I, T]
T
はArrayの要素の型を表すので任意の型を指定できるとして、I
には何を指定できるのか。他の多くのNimの組み込み型と同様にarrayジェネリクスはNimコードとしては実装されておらず、コンパイラコード内で実装されている(ややこしいことにコンパイラはNimで書かれているのである意味Nimコードで実装されているとも言える)。そこでコンパイラコード内を探してみたところ、I
についてチェックを行っていると思われるコード(semtypes.nim)を見つけた。
簡単にまとめると
- range型(a..b形式で記述されているもの)
- int型
- 定数式
これらのものがI
に指定可能だと思われる。そして、どの形式でも最終的にはrange型へ変換しているようだった。
動的配列(seq型)を利用する
(seq型) seq[要素型] (seq型のオブジェクトを作成(arrayコンストラクタで作成したものを@演算子でseq化している)) @[要素1, 要素2, ...]
var x: seq[int] x = @[1, 2, 3, 4, 5, 6] for value in x: # Pythonのようにfor文で走査できる echo value for index, value in x: # 2つのパラメータだとインデックスと値を取得できる echo "Index=", index, " Value=", value
基本的にはarray型の上位互換なので「固定長配列(array型)を利用する」も参照。seq型の初期値は空配列(@[])ではなく、nilなことに注意。
標準で用意されているseq型のプロシージャ(array型にはないもの)
- newSeq : 長さを指定して新しいseqを作成する。変数を引数にとり、新しいseqはその変数へセットされる
- newSeq : 長さを指定して新しいseqを作成する。新しいseqは戻り値として渡される
- newSeqOfCap : キャパシティを指定して長さ0の新しいseqを作成する。新しいseqは戻り値として渡される
- setLen : 長さを変更する
- add : 要素を追加する
- del : 末尾要素を指定のインデックスの位置へ移し上書きする。(結果的に要素数は1つ減る)
- delete : 指定のインデックスの要素を取り除く。(delよりこっちの方が一般的な挙動)
- insert : 指定のインデックスの位置に要素を追加する
- isNil : seqがnilかどうかを取得
- `&` : seqを連結する
- pop : 末尾の要素を削除する
- shallow : 代入時にshallowコピーを行うようにする。
- safeAdd : 要素を追加する。seqがnilの場合には最初にseqを作成する。
ドキュメント :
Array and sequence types
可変個引数を利用する
varargs[引数の型] varargs[引数の型,変換用のプロシージャル型]
- プロシージャで可変個引数を扱うには、引数の型としてvarargsを使う
- 渡された可変個引数はarrayに変換される
- varargsはプロシージャの最後の引数としてしか指定できない
- varargsの2番目のジェネリクスパラメータとしてプロシージャル型を渡すことで、可変個引数の各要素にたいして任意の変換処理を行わせることができる
- 可変個引数としてarray型を渡すと自動で展開されて個別の引数として渡される
proc test(a: varargs[string]) = for s in a: echo s test( "a", "i", "u" ) # 出力: # a? # i? # u? test( ["a", "i", "u"] ) # array型は自動展開されるので↑と同じ引数を渡したことになる # 出力: # a # i # u proc test2[T](a: varargs[T]) = for s in a: echo repr(s) test2( "a", "i", "u" ) # Tはstring型になる test2( ["a", "i", "u"] ) # Tはstring型になる # arrayのまま渡したい場合はさらにarray化する test2( [["a", "i", "u"]] ) # Tはarray[string]型になる # 変換プロシージャを用意(末尾に?を追加する例) proc t(x:string): string = x&"?" # 変換処理を指定 proc test3(a: varargs[string,t]) = for s in a: echo s test3( "a", "i", "u" ) # 出力: # a? # i? # u?
ドキュメント :
Varargs
タプルを利用する
- Nimのタプルは他のプログラミング言語と違いフィールド名を付けることができる
- フィールド名が違う場合、違う型として扱われる
- タプルからタプルに代入した場合、それぞれの要素がコピーされる
- フィールド名と型とその順番が同じものは同じ型として扱われる
- フィールド名が無いタプル型も作成することができる
- フィールド名なしのタプル型とフィールド名ありのタプル型は、型と順番され合っていれば代入などの操作が可能(これが仕様かどうかは不明)
【タプル型】 tuple[フィールド名: 型, フィールド名: 型,...] # フィールド名ありの場合 (型,型,型,...) # フィールド名なしの場合 # Object型風の型定義も可能 type 新規型名 = tuple フィールド名: 型 フィールド名: 型 ... 【タプルコンストラクタ】 (フィールド名:値, フィールド名:値) (値, 値) 【フィールドへのアクセス】 タプルオブジェクト.フィールド名 タプルオブジェクト[フィールドインデックス] 【タプルの中身を変数で受け取る(tuple unpacking)】 var (変数名1, 変数名2, 変数名3, ...) = タプルオブジェクト var (変数名1, _, 変数名2, ...) = タプルオブジェクト # 不要な要素には _ を使用することで必要な要素だけ受け取ることができる
var x = (id:"hoge",age:10) var y = ("hoge2",10) # コンストラクタでのフィールド名は省略可能 var z = (id:"hoge3",height:150) var w = (age:30,id:"hoge4") x = y # 型と名前、そしてその順番が一致しているので代入可能 # x = z # 名前が一致していないものがあるので代入不可 # x = w # 型と名前が一致していても順番が一致していないので代入不可 # フィールドアクセス echo x.id # hoge2 # []でもアクセスできる echo x[0] # hoge2 # タプル(フィールド名無し)を返すプロシージャ proc getTuple(): (int, bool) = return (10, true)
ドキュメント :
Tuples and object types
Tuple unpacking
Tuples(チュートリアル)
Object型を利用する
- Object型はタプルに以下のようなオブジェクト指向プログラミングの機能を追加したようなもの
- 型の継承
- 非公開フィールド
- 独自の型(暗黙でdistinctが付くイメージ?)
- Object型名()でオブジェクトを構築できる(object construction expression)
- 引数を渡す場合はフィールド名指定が必須
- Object型がref付きの場合、暗黙にsystem.newが呼ばれる
{.final.}
プラグマで派生を禁止させることができる- RootObj型を継承していないObject型は継承不可になる。ただし、
{.inheritable.}
プラグマをつければ継承は可能 - refを付けない場合、派生型を基底型へ代入すると派生型のフィールドが切り捨てられてしまうことに注意(C++でいうオブジェクトスライス問題)
- 非公開フィールドは同じモジュール内ならアクセス可能
- C++でいうメンバ関数というものは存在しない。ただし、メソッド記法を使うことでプロシージャをメンバ関数のように呼び出すことは可能
【Object型定義】 type # 新規Object型を定義する 新規オブジェクト型名 = object フィールド名: 型名 # 公開フィールド フィールド名: 型名 # 公開フィールド ... # 派生型定義 新規オブジェクト型名 { .final. } = object of 基底型 # { .final. }は任意 フィールド名*: 型名 # 公開フィールド フィールド名: 型名 # 非公開フィールド ... 【Object型構築】 var 変数名 = オブジェクト型名(フィールド名:値,フィールド名:値,...)
type # Object型を定義 MyObj = ref object of RootObj x: int y: int # 派生型を定義 MyObj2 = ref object of MyObj z: int proc test(hoge:MyObj) = echo hoge.x, ", ", hoge.y # オブジェクト構築 var obj: MyObj = MyObj(x:1, y:2) var obj2: MyObj2 = MyObj2(y:20, z:30) # メンバ関数のように呼び出すことが可能 obj.test() obj2.test()
ドキュメント :
Tuples and object types
Object construction
Set型
- いわゆる集合型(同じ値は複数格納できない)
- 要素の型は以下のものに限られる
- int8-int16
- uint8/byte-uint16
- char
- enum
- 集合型というよりフラグ管理を想定しているっぽい
【Set型】 set[要素の型] 【Set型コンストラクタ】 {値,値,...} {値..値,...}
var x: set[int8] var baisu = {3,6,9,12,15,18,21,24,27,30,33,36,39} # 3の倍数 var three = {3,13,23,30..39} # 3のつく数字 var aho = baisu + three # 和集合を作成 for i in 0..40: if i in aho: echo i, " !!!" else: echo i
Set型のプロシージャ
- `==` : 比較
- `<=` : 比較
- `<` : 比較
- incl : 含める(追加する)
- excl : 除外する(削除する)
- card : 要素数を取得(正確には集合の濃度?)
- `*` : 積集合を取得
- `+` : 和集合を取得
- `-` : 差集合
- contains : 含まれるかどうかを取得
- `$` : 文字列化
- items : for文用
- `in` : 含まれるかどうかを取得
- `notin` : 含まれないかどうかを取得
ドキュメント :
Set type
参照を利用する
- 変数はデフォルトでは参照ではなく値(代入はコピーが発生する)
- 型名に
ref
を付けることで参照型の変数を定義できる - 参照型はGC対象のヒープ上のオブジェクト(traced object)への参照を持つ(それ以外のオブジェクトへの参照は持てない)
- 参照型の参照先を得る(デリファレンスする)には
[]
演算子を使う - 参照型への
.
演算子と[]
演算子(インデックス演算子)は暗黙にデリファレンスされる - プロシージャの最初の引数として参照型を渡す場合、暗黙にデリファレンスされる(ただし{.experimental.}プラグマ指定が必要)
- traced objectを作成するにはnewプロシージャを使う
- 参照型が
nil
の場合、その参照はどこも指していないことを表す - 参照型で値型(非参照型)の参照を行うことはできない(と思われる)
# 参照型変数を定義 var x: ref int assert x == nil # まだどこも参照していない x = int.new # newプロシージャでtraced objectを作成し参照型変数へ代入 x[] = 10 # デリファレンスは[]演算子を使う echo x[] # 同じ参照先を指す参照型変数を定義してみる var x2 = x x2[] = 20 # 参照先の中身を変更 assert x[] == 20 # xとx2は同じ参照先なのでxの参照先の中身も変更される # 引数として参照を受け取ることもできる proc test(a:ref int) = echo a[] test( x ) #[ 参照ではないのでエラー test( 10 ) var v = 10 test( v ) ]#
ドキュメント :
Reference and pointer types
無名プロシージャを利用する
- 無名プロシージャを使うことでプロシージャを値(式)として作成できる
- 無名プロシージャはクロージャでもある(「クロージャを利用する」も参照)
- いろいろな記法があるのでまとめました -> 【Nim】無名プロシージャの書き方まとめ
【無名プロシージャ】
proc(引数リスト): 戻り値型 = 文リスト
# 無名プロシージャを作成し、変数へ代入 var p = proc( x:int ): int = return x + 1 echo p( 10 ) # 11
ドキュメント :
Anonymous Procs
クロージャを利用する
- モジュールトップレベル以外で定義されたプロシージャ(無名プロシージャ、名前付きプロシージャ)はクロージャとなる
- クロージャはクロージャを内包するスコープのローカル変数へアクセスできる
- キャプチャされた変数は"環境"としてクロージャへの隠し引数として渡される
- キャプチャされた変数は参照としてアクセスされる
- "環境"用のメモリはヒープ上に確保されるが、可能な場合はスタック上で確保される
proc getClosure(n:string): proc() = var x: int var name = n return proc() = x = x + 1 echo name, " : ", x #[ 普通の名前付きのプロシージャもプロシージャル型として渡せばクロージャになる proc hoge() = x = x + 1 echo name, " : ", x return hoge ]# var c = getClosure( "c" ) var c2 = getClosure( "c2" ) # クロージャはそれぞれの環境を保持しているので、それぞれが呼び出されるたびに数字が増える c() # c : 1 c() # c : 2 c2() # c2 : 1 c() # c : 3
ドキュメント :
Closures
Anonymous Procs
ジェネリクスを利用する
- ジェネリクスはC++のテンプレートのようなもの
- ジェネリクスを使うことで型をパラメータ化したプロシージャ、イテレータ、型を定義することができる
- 基本的には、定義時、利用時、それぞれで名前のあとに[]で型引数リストを付けるだけ
- ジェネリクスプロシージャの型引数は、プロシージャの引数から推論が働くので省略できる場合がある(C++のテンプレートと同様)
【ジェネリクスプロシージャ(イテレータ)の定義】 proc プロシージャ名[型引数リスト](引数リスト): 戻り値型 = 文リスト 【ジェネリクスプロシージャ(イテレータ)の呼び出し(とインスタンス化)】 プロシージャ名[型引数リスト](引数リスト) 【ジェネリクス型の定義】 type ジェネリクス型名[型引数リスト] = 型定義 【ジェネリクス型の利用】 var hoge: ジェネリクス型名[型引数リスト]
# ジェネリクスプロシージャを定義 proc test[T1,T2](x:T1,y:T2) = echo "T1=", typetraits.name(T1), "(", x ,")", " T2=", typetraits.name(T2), "(", y ,")" # ジェネリクスプロシージャを呼び出し(明示的に型引数を与える) test[int,string](10,"aaa") # T1=int(10) T2=string(aaa) # ジェネリクスプロシージャを呼び出し(型引数をプロシージャ引数から推論してもらう) test("bbb",30) # T1=string(bbb) T2=int(30) #[ ジェネリクスプロシージャは型引数を与えるまで実体が存在しないのでプロシージャル型(関数ポインタ)として扱えない var p = test ]# var p = test[int,string] # 型引数を与えて実体化すればプロシージャル型として扱える p( 40,"ccc" ) # T1=int(40) T2=string(ccc) # ジェネリクスオブジェクト型を定義 type MyObj[T] = ref object of RootObj x: T var o = MyObj[int]() echo typetraits.name( type( o.x ) ) # int #[ オブジェクトコンストラクタには型引数の推論はないようだ var o2 = MyObj( x:"aaa" ) # コンパイルエラー ]# var o2 = MyObj[string]( x:"aaa" ) echo typetraits.name( type( o2.x ) ) # string # メソッドコール記法の注意 type A = object proc test_template[T]( value: A ) = echo T.name var a = A() # a.test_template[int]() # (a.test_template)[int]と解釈されてしまうのでコンパイルエラーになる a.test_template[:int]() # [int]ではなく[:int]とすることで回避できる
ドキュメント :
Generics
テンプレートを利用する
- テンプレートはAST(抽象構文木)を置換できる仕組み
- C言語でいう関数マクロと似ている(ただし、テキストではなくASTを置換する)
- テキストの置換ではないので、ASTとして表現できないものをテンプレートの引数として渡すことはできない
- テンプレートはプロシージャのように定義、呼び出しを行うことができる
- 演算子形式やメソッド記法での呼び出しにも対応している
- 実際、組み込み演算子である
!=, >, >=, in, notin, isnot
はテンプレートで実装されている
- 実際、組み込み演算子である
- テンプレートの引数に指定する型として通常の型の他に、
untyped
,typed
,typedesc
が使える(これらはメタ型(meta types)と呼ばれる)untyped
はテンプレート引数の型解決をテンプレート呼び出し時には行わないようにさせるときに使うtyped
はテンプレート引数の型解決をテンプレート呼び出し時に行うようにさせるときに使うtypedesc
はテンプレート引数として型名を指定させたいときに使う
- テンプレート呼び出し時に呼び出し式の後に
:
とそれに続けてブロック文を記述することで、テンプレートの最後の引数としてブロック文を渡すことができる - テンプレートでも可変個引数を利用可能。その引数を
untyped
にさせたい場合はvarargs[untyped]
とする- テンプレートでの可変個引数はfor文などで走査することはできない(マクロならできる)
- テンプレートで展開されたコードのスコープはテンプレートが定義された場所のスコープになる -> Symbol binding in templates
- テンプレートコード内で``を使うことでテンプレート引数と文字列を結合させることができる(C言語マクロでいう##的な…) -> Identifier construction
- テンプレート仮引数は同名の外部の識別子(変数名など)を隠蔽する(通常のプロシージャと同じく)
- 同名の外部の識別子を使いたい場合はbind文を使う -> Lookup rules for template parameters
【テンプレート定義】 template テンプレート名(引数リスト): 戻り値型 = 文リスト 【テンプレート呼び出し】 テンプレート名(引数リスト) # 普通に呼び出し 第一引数 テンプレート名 第二引数 # 演算子形式で呼び出し 第一引数.テンプレート名(残りの引数リスト) # メソッド記法で呼び出し テンプレート名(引数リスト): # :のあとに続くブロック文は最後の引数として渡される ブロック文
# 渡されたブロック文全体を文字列として出力するコードを追加するテンプレート template echoBlock( blockName, x:untyped ): untyped = echo "====== ", astToStr(blockName), " ======" echo astToStr( x ) echo "\n==================" x echoBlock(test): # :を使ってブロック文ごと渡す var x: int x = 10 echo x
実行結果
====== test ====== var x: int x = 10 echo x ================== 10
ドキュメント :
Templates
マクロを利用する
- マクロを使うことでコンパイル時にプログラマブルにASTを構築することができる
- マクロはプロシージャと同じような形で定義、呼び出すことができる
- ASTを構築するためにmacrosモジュールをインポートする必要がある
- プロシージャにプラグマとしてマクロ名を指定することで、そのプロシージャを引数として渡してマクロを起動させることもできる
import macros macro mymacro(): untyped = return nnkCommand.newTree( newIdentNode(!"echo"), newLit("hello") ) mymacro() # echo "hello"と展開される
ドキュメント :
Macros
モジュールを利用する
- モジュールによってプログラムを分割することができる
- モジュールは個別のファイルによって定義される(そしてファイル名がモジュール名になる)
- モジュールごとに名前空間を持つ
- import文を使うことで他のモジュールにアクセスすることができる
- モジュールトップレベルに存在する
*
が付いたシンボルは他のモジュールからアクセスが可能になる - export文を使うことで、export文を使ったモジュールがインポートされたときに自動でそのモジュールもインポートされる
【import文】 import モジュール名 # 普通にインポート import モジュール名 except シンボル名 # 一部のシンボルを除いてインポート import モジュール名 as 別名 # モジュールをインポートし、別名でアクセスできるようにする from モジュール名 import シンボル名 # 一部のシンボルのみインポートする from モジュール名 import nil # シンボル名をnilにすることでモジュール名をつけることを強制させることができる 【export文】 export モジュール名
ドキュメント :
Modules
例外を利用する
try: # 例外処理対象となる節 discard except ErrorA: # 特定の例外をキャッチ discard except ErrorB, ErrorC: # 複数の例外をキャッチ discard except ErrorD as e: # 特定の例外をキャッチ( そして例外インスタンスを変数で受け取る ) discard except: # その他の例外をキャッチ let e = getCurrentException() # 現在の例外を取得 echo getCurrentExceptionMsg() # 現在の例外の文字列表現を取得 finally: # 例外発生の有無によらず必ず最後に実行される節(省略可能) discard let x = try: parseInt("133a") except: -1 # 例外を文ではなく式として扱うこともできる # 独自の例外型を作る type LoadError* = object of Exception raise newException(LoadError, "Failed to load data") # 例外を発生させる
ドキュメント :
Exception handling
Option型を利用する
import std/options import std/sugar {.experimental.} # 割り算のプロシージャ。割る数が0なら値なしを返す(none(int)) proc calcDiv( x: int, y: int ): Option[int] = if y == 0: return none(int) return some (x / y).toInt # mapで値を取り出す(値が存在する場合に呼び出されるプロシージャを渡す) calcDiv(10, 1).map proc(value: int) = echo value # mapに渡す無名プロシージャの定義に=>記法を使用(std/sugarが必要) calcDiv(10, 2).map (value: int) => echo value # mapに渡す無名プロシージャの定義にdo記法を使用({.experimental.}が必要) calcDiv(10, 3).map do(value: int): echo value
ドキュメント :
std/options
マクロ
ドキュメント :
macrosモジュール
定数
型の種類一覧
NimTypeKind = enum
ntyNone, ntyBool, ntyChar, ntyEmpty, ntyAlias, ntyNil, ntyExpr, ntyStmt, ntyTypeDesc,
ntyGenericInvocation, ntyGenericBody, ntyGenericInst, ntyGenericParam,
ntyDistinct, ntyEnum, ntyOrdinal, ntyArray, ntyObject, ntyTuple, ntySet, ntyRange,
ntyPtr, ntyRef, ntyVar, ntySequence, ntyProc, ntyPointer, ntyOpenArray, ntyString,
ntyCString, ntyForward, ntyInt, ntyInt8, ntyInt16, ntyInt32, ntyInt64, ntyFloat,
ntyFloat32, ntyFloat64, ntyFloat128, ntyUInt, ntyUInt8, ntyUInt16, ntyUInt32,
ntyUInt64, ntyUnused0, ntyUnused1, ntyUnused2, ntyVarargs, ntyUnused, ntyError,
ntyBuiltinTypeClass, ntyUserTypeClass, ntyUserTypeClassInst,
ntyCompositeTypeClass, ntyInferred, ntyAnd, ntyOr, ntyNot, ntyAnything, ntyStatic,
ntyFromExpr, ntyFieldAccessor, ntyVoid
シンボルの種類一覧
NimSymKind = enum
nskUnknown, nskConditional, nskDynLib, nskParam, nskGenericParam, nskTemp,
nskModule, nskType, nskVar, nskLet, nskConst, nskResult, nskProc, nskMethod,
nskIterator, nskConverter, nskMacro, nskTemplate, nskField, nskEnumField, nskForVar,
nskLabel, nskStub
ノードの種類一覧
NimNodeKind = enum
nnkNone, nnkEmpty, nnkIdent, nnkSym, nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit,
nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, nnkUInt16Lit,
nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, nnkFloat32Lit, nnkFloat64Lit,
nnkFloat128Lit, nnkStrLit, nnkRStrLit, nnkTripleStrLit, nnkNilLit, nnkMetaNode,
nnkDotCall, nnkCommand, nnkCall, nnkCallStrLit, nnkInfix, nnkPrefix, nnkPostfix,
nnkHiddenCallConv, nnkExprEqExpr, nnkExprColonExpr, nnkIdentDefs, nnkVarTuple,
nnkPar, nnkObjConstr, nnkCurly, nnkCurlyExpr, nnkBracket, nnkBracketExpr,
nnkPragmaExpr, nnkRange, nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr, nnkIfExpr,
nnkElifExpr, nnkElseExpr, nnkLambda, nnkDo, nnkAccQuoted, nnkTableConstr, nnkBind,
nnkClosedSymChoice, nnkOpenSymChoice, nnkHiddenStdConv, nnkHiddenSubConv, nnkConv,
nnkCast, nnkStaticExpr, nnkAddr, nnkHiddenAddr, nnkHiddenDeref, nnkObjDownConv,
nnkObjUpConv, nnkChckRangeF, nnkChckRange64, nnkChckRange, nnkStringToCString,
nnkCStringToString, nnkAsgn, nnkFastAsgn, nnkGenericParams, nnkFormalParams,
nnkOfInherit, nnkImportAs, nnkProcDef, nnkMethodDef, nnkConverterDef, nnkMacroDef,
nnkTemplateDef, nnkIteratorDef, nnkOfBranch, nnkElifBranch, nnkExceptBranch,
nnkElse, nnkAsmStmt, nnkPragma, nnkPragmaBlock, nnkIfStmt, nnkWhenStmt, nnkForStmt,
nnkParForStmt, nnkWhileStmt, nnkCaseStmt, nnkTypeSection, nnkVarSection,
nnkLetSection, nnkConstSection, nnkConstDef, nnkTypeDef, nnkYieldStmt, nnkDefer,
nnkTryStmt, nnkFinally, nnkRaiseStmt, nnkReturnStmt, nnkBreakStmt, nnkContinueStmt,
nnkBlockStmt, nnkStaticStmt, nnkDiscardStmt, nnkStmtList, nnkImportStmt,
nnkImportExceptStmt, nnkExportStmt, nnkExportExceptStmt, nnkFromStmt,
nnkIncludeStmt, nnkBindStmt, nnkMixinStmt, nnkUsingStmt, nnkCommentStmt,
nnkStmtListExpr, nnkBlockExpr, nnkStmtListType, nnkBlockType, nnkWith, nnkWithout,
nnkTypeOfExpr, nnkObjectTy, nnkTupleTy, nnkTupleClassTy, nnkTypeClassTy,
nnkStaticTy, nnkRecList, nnkRecCase, nnkRecWhen, nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy, nnkDistinctTy, nnkProcTy, nnkIteratorTy, nnkSharedTy,
nnkEnumTy, nnkEnumFieldDef, nnkArglist, nnkPattern, nnkReturnToken, nnkClosure,
nnkGotoState, nnkState, nnkBreakState
その他
ノード情報操作/取得
- sameType : ノードの種類が同じかどうかを取得
- add : 子ノードを追加
- insert : 子ノードを挿入
- del : 指定の子ノードを削除
- len : 子ノードの数を取得
[]
: 子ノードを取得- last : 末尾の子ノードを取得
- getType : ノードの型を取得
- getTypeInst : ノードの型を取得(インスタンス化されたジェネリクスのジェネリクス引数を含む)
- getTypeImpl : ノードの型を取得(ジェネリクス引数を含む)
- kind : ノードの種類(NimNodeKind)を取得(getType系プロシージャで取得したノードに対して使う)
- typeKind : ノードの型の種類(NimTypeKind)を取得(getType系プロシージャで取得したノードに対して使う)
- getImpl : シンボルの実装を取得
- lineInfoObj : ノードのソースコード行情報を取得
- getAst : マクロ展開結果をASTノード化
- expectKind : ノードの種類をチェックする
- expectMinLen : 子ノードの最低数をチェックする
- expectLen : 子ノードの数をチェックする
- プロシージャノード用
- body : ブロック文を取得
- basename : 単項演算子の対象識別子を取得
- eqIdent : 同一識別子かどうかを取得
- hasArgOfName : 指定の仮引数名が存在するかどうかを取得
- findChild : 子ノードを検索
ASTノードを文字列化する
- マクロ内で使う(NimNodeを渡す)
- treeRepr : ツリー表示
- lispRepr : LISP風表示
- astGenRepr : ノード生成コード
- マクロ外で使う(コードブロックを渡す)
- dumpTree : ツリー表現を標準出力する
- dumpLisp : LISP風表現を標準出力する
- dumpAstGen : ノード生成コード表現を標準出力する
ASTノードを作成する
作成するノード | 使用するプロシージャ |
---|---|
任意のノードを作成 | proc newNimNode(kind: NimNodeKind; lineInfoFrom: NimNode = nil): NimNode {..} |
ノードから文字列リテラルノードを作成 | proc toStrLit(n: NimNode): NimNode {..} |
コードからノード生成 | proc quote(bl: typed; op = "``"): NimNode {..} |
文字列からノード生成 |
proc parseExpr(s: string): NimNode {..} proc parseStmt(s: string): NimNode {..} |
マクロの呼び出し元の式のノードを取得 | proc callsite(): NimNode {..} |
ノード単体をコピー | proc copyNimNode(n: NimNode): NimNode {..} |
ノードツリーをコピー | proc copyNimTree(n: NimNode): NimNode {..} proc copy(node: NimNode): NimNode {..} |
子ノードをコピー | proc copyChildrenTo(src, dest: NimNode) {..} |
空ノード | proc newEmptyNode(): NimNode {..} |
文リスト | proc newStmtList(stmts: varargs[NimNode]): NimNode {..} |
カッコ式 | proc newPar(exprs: varargs[NimNode]): NimNode {..} |
ブロック文(ラベル付き) | proc newBlockStmt(label, body: NimNode): NimNode {..} |
ブロック文 | proc newBlockStmt(body: NimNode): NimNode {..} |
var定義 | proc newVarStmt(name, value: NimNode): NimNode {..} |
let定義 | proc newLetStmt(name, value: NimNode): NimNode {..} |
const定義 | proc newConstStmt(name, value: NimNode): NimNode {..} |
バインドされたシンボル? | proc bindSym(ident: string; rule: BindSymRule = brClosed): NimNode {..} |
新規シンボル(genSym) | proc genSym(kind: NimSymKind = nskLet; ident = ""): NimNode {..} |
代入文 | proc newAssignment(lhs, rhs: NimNode): NimNode {..} |
ドット式 | proc newDotExpr(a, b: NimNode): NimNode {..} |
コロン式 | proc newColonExpr(a, b: NimNode): NimNode {..} |
後置き演算式化 | proc postfix(node: NimNode; op: string): NimNode {..} proc unpackPostfix(node: NimNode): tuple[node: NimNode, op: string] {..} (式を展開) |
前置き演算式化 | proc prefix(node: NimNode; op: string): NimNode {..} proc unpackPrefix(node: NimNode): tuple[node: NimNode, op: string] {..} (式を展開) |
中置き演算式化 |
proc infix(a: NimNode; op: string; b: NimNode): NimNode {..} proc unpackInfix(node: NimNode): tuple[left: NimNode, op: string, right: NimNode] {..} (式を展開) |
変数定義 | proc newIdentDefs(name, kind: NimNode; default = newEmptyNode()): NimNode {..} |
nilリテラル | proc newNilLit(): NimNode {..} |
プロシージャ定義 | proc newProc(name = newEmptyNode(); params: openArray[NimNode] = [newEmptyNode()]; body: NimNode = newStmtList(); procType = nnkProcDef): NimNode {..} |
if文 | proc newIfStmt(branches: varargs[tuple[cond, body: NimNode]]): NimNode {..} |
識別子 |
proc newIdentNode(i: string): NimNode {..} proc newIdentNode(i: NimIdent): NimNode {..} proc ident(name: string): NimNode {..} # 内部でnewIdentNodeを呼んでいるだけ |
コメント文 | proc newCommentStmtNode(s: string): NimNode {..} |
各種リテラル | proc newLit(各種リテラル): NimNode {..} |
プロシージャ呼び出し |
proc newCall(theProc: NimNode; args: varargs[NimNode]): NimNode {..} proc newCall(theProc: NimIdent; args: varargs[NimNode]): NimNode {..} proc newCall(theProc: string; args: varargs[NimNode]): NimNode {..} |
ツリーノード | proc newTree(kind: NimNodeKind; children: varargs[NimNode]): NimNode {..} |
カッコ式 | proc newPar(exprs: varargs[NimNode]): NimNode {..} |
デバッグ
標準出力を行う : echo
echo( "aaa" ) echo( 123 ) # 自動で文字列変換される($演算子が呼ばれる) echo( 123, 456, 789 ) # 複数引数を与えることができる(連結される) echo "aaa" # プロシージャなので()を省略できる debugEcho( "aaa" ) # debugEchoはデバッグ時にのみ機能する writeLine( stdout, "aaa" ) # こういう方法もある(echoは内部でwriteLineを使っている)
ドキュメント :
echo
debugEcho
writeLine
その他
Nimのバージョン情報を取得する : NimVersion
echo NimVersion # 0.17.0
ドキュメント :
NimVersion
コマンドライン引数を取得する : os.commandLineParams()
import os # コマンドライン引数を個別に取得(0番目はプログラム名) for i in 0..os.paramCount(): echo os.paramStr(i) # まとめて取得する(0番目はプログラム名ではない) for i in os.commandLineParams(): echo i
ドキュメント :
commandLineParams
コマンドライン引数を解析(パース)する : parseoptモジュール
Pythonのargparseとは使い方が異なることに注意。引数を走査して自分で値を取得する必要がある。
import os import strutils import parseopt var inputFilePath: string # --input または -i var outputFilePath: string # --output または -o var opt = parseopt.initOptParser( os.commandLineParams().join(" ") ) for kind, key, val in opt.getopt(): case key of "input", "i": # --input または -i case kind of parseopt.cmdLongOption, parseopt.cmdShortOption: opt.next() # --input または -iの次の引数に移動 inputFilePath = opt.key # 位置引数の値はvalではなくkeyに入っている else: discard of "output", "o": case kind of parseopt.cmdLongOption, parseopt.cmdShortOption: opt.next() outputFilePath = opt.key else: discard
ドキュメント:
parseopt
型を文字列化する
typetraits.name
プロシージャを使う
import typetraits echo typetraits.name( int ) # int proc echoType[T]() = echo( typetraits.name( T ) ) proc echoType[T](x:T) = echo( typetraits.name( T ) ) echoType[int]() # int echoType[string]() # string echoType(10) # int echoType("hoge") # string # 型名じゃない場合はtype演算子を使ってtypedesc型へ変換してから呼ぶ echo typetraits.name( type( 1 + 3 ) ) # int
プロシージャの引数で型(型名)を受け取る
引数の型としてtypedescを指定すると、型(型名)で受け取れる。この場合、仮引数名は型を表すようになる。
import typetraits proc echoType(x:typedesc) = var hoge: x # xは型を表す echo typetraits.name( x ) # typetraits.nameも引数がtypedescになっているので型を渡せる echoType(int) # 出力:int int.echoType # メソッド構文も使える # typedescは型しか受け付けないので、普通の式を渡してもコンパイルエラーになる # echoType(1+2) # エラー echoType(type(1+2)) # type演算子を使えば一応渡せる # 普通の型を受け取るプロシージャをオーバーロードすることで、どちらでも渡せるようになる proc echoType[T](x:T) = echo typetraits.name( T ) echoType(int) # 出力:int echoType(1+2) # 出力:int
ちなみに、typedesc
を変数の型として指定することはできないようだ
var x: typedesc # コンパイルエラー
ドキュメント :
typedesc
式の型を得る
var x : type( 1 + 2 ) # x は int型になる
ドキュメント :
Type operator
式のASTを出力する
treeReprプロシージャ(Macro専用)を使う
import macros macro echoTree( x:untyped ): untyped = echo treeRepr( x ) echoTree( 1+3 )
出力
Infix Ident !"+" IntLit 1 IntLit 3
ドキュメント :
treeRepr
変数のアドレスをuint64型で取得する
addr演算子で取得したptr T
型をuint64型へキャストすればよさそう(正しくアドレスが取れているのかどうかは不明…)
proc getAddrInt[T](x:var T): uint64 = cast[uint64](addr(x)) var hoge: int var hoge2: string echo getAddrInt( hoge ) echo getAddrInt( hoge2 )
ドキュメント :
`addr`
任意の型のデフォルト値を取得する
int型などのデフォルト値を取得する方法がわからなかったので*1、プロシージャとして作ってみました。もっと良い方法があるかもしれません。
proc getDefault(T:typedesc): T = (var temp:T;temp) proc getDefault[T](x:T): T = (var temp:T;temp) echo int.getDefault # 0 echo "aaa".getDefault # nil echo 123.getDefault # 0
任意のコードがコンパイルが可能かどうかを取得する
compilesプロシージャを使う。正確にはコンパイル可能かどうかではなく、パース後の意味解析でエラーが出ないかどうかしか取得できない。パースができないコードは普通にコンパイルエラーが発生してしまう(compilesプロシージャに渡す前にコンパイルエラーが発生してしまうため)。
when( compiles( 1 + abc ) ): echo "can compile" else: echo "can't compile" # このように書けばブロック文も渡せる when compiles((block: discard 1 + 2 if true: echo "aaa" )): echo "can compile" else: echo "can't compile"
ドキュメント :
compiles
複数の識別子を繋げて1つの識別子を作る
複数の識別子を空白で繋げて``でくくると、1つの繋がった識別子として扱われる。テンプレートでこの機能を使うことで新しい識別子を作ることができる。これはC言語マクロでいうところの##
と同じような機能。
template join( a, b:untyped ): untyped = `a b` var join( abc, def ): int # abcとdefという文字を繋げてabcdefという識別子を作成 abcdef = 10 echo join( abc, def ) #[ 与える文字は識別子として正しくないといけないので、abc_ という文字は使えない(Nimは識別子の先頭や末尾に_は使えない仕様) var join( abc_, def ): int # abc_def ]#
ドキュメント :
Identifier construction
外部コマンドを実行
import os var retCode = os.execShellCmd( "echo aaa" )
ドキュメント:
os.execShellCmd
ファイル
ファイルの存在チェック : os.fileExists( "path" )
import std/os if os.fileExists( "path" ): discard
ドキュメント:
fileExists
ファイル読み込み : io.readFile( "path" )
# import 不要 let fileContent = io.readFile( "path" )
ドキュメント:
readFile
ファイル書き込み : io.writeFile( "path", fileContent )
# import 不要 io.writeFile( "path", fileContent )
ドキュメント:
writeFile
ファイル削除 : os.remove( "path" )
import std/os os.removeFile( "path" )
ドキュメント:
removeFile
ファイルコピー : os.copyFile( src_path, dest_path )
import std/os
os.copyFile( src_path, dest_path )
ドキュメント:
copyFile
ファイル名変更 : os.moveFile( old_filename, new_filename )
import std/os os.moveFile( old_filename, new_filename ) # moveFileをファイル名変更に使える
ドキュメント:
moveFile
ファイル移動 : os.moveFile( src_path, dest_path )
import std/os
os.moveFile( src_path, dest_path )
ドキュメント:
moveFile
ファイル更新日取得 : os.getLastModificationTime( "path" )
import std/os let time = os.getLastModificationTime( "path" )
ドキュメント:
getLastModificationTime
ファイル更新日変更 : os.setLastModificationTime( "path", time )
import std/os import std/times let time = now().toTime() os.setLastModificationTime( "path", time )
ドキュメント:
setLastModificationTime
指定のディレクトリ内のファイルリストを取得
import std/os import std/sugar # collectで必要 #[ 1行で書こうとしたけど、コンパイルエラーになった let targetFiles = collect( for i in os.walkDir( "path" ): if i.kind == pcFile: i.path ) ]# let targetFiles = collect: for i in os.walkDir( "path" ): if i.kind == pcFile: i.path
ドキュメント:
walkDir
指定のディレクトリ内のファイルリストを取得(再帰的) : collect( for i in os.walkDirRec( "path" ): i )
import std/os import std/sugar # collectで必要 let targetFiles = collect( for i in os.walkDirRec( "path" ): i )
ドキュメント:
walkDirRec
ディレクトリ
ディレクトリ作成 : os.createDir( "path" )
import std/os os.createDir( "path" )
ドキュメント:
createDir
ディレクトリ作成( 中間ディレクトリも作成 ) : os.createDir( "path" )
import std/os os.createDir( "path" ) # createDirで中間ディレクトリも作成される
ドキュメント:
createDir
ディレクトリ削除 : os.removeDir( "path" )
import std/os os.removeDir( "path" )
ドキュメント:
removeDir
カレントディレクトリの取得 : os.getCurrentDir()
import std/os let cwd = os.getCurrentDir()
ドキュメント:
getCurrentDir
カレントディレクトリの変更 : os.setCurrentDir( "path" )
import std/os os.setCurrentDir( "path" )
ドキュメント:
setCurrentDir
パス操作
パスをつなげる : os.joinPath( "path1", "path2" )
import std/os os.joinPath( "path1", "path2" )
ドキュメント:
joinPath
ファイルパスから絶対パスを取得する : os.absolutePath( "path" )
import std/os os.absolutePath( "path" )
ドキュメント:
absolutePath
ファイルパスからファイル名(拡張子付き)を取得する : os.extractFilename( "path" )
import std/os let fileName = os.extractFilename( "path" )
ドキュメント:
extractFilename
ファイルパスからファイル名(拡張子なし)を取得する : os.splitFile( "path" ).name
import std/os let fileName = os.splitFile( "path" ).name
ドキュメント:
splitFile
ファイルパスからディレクトリ名を取得する : "path".splitFile().dir.splitFile().name
import std/os let dirName = "path".splitFile().dir.splitFile().name
ドキュメント:
splitFile
ファイルパスからディレクトリパスを取得する : os.splitFile( "path" ).dir
import std/os let dirPath = os.splitFile( "path" ).dir
ドキュメント:
splitFile
ファイルパスから拡張子を取得する : os.splitFile( "path" ).ext
import std/os let ext = os.splitFile( "path" ).ext
ドキュメント:
splitFile
ファイルパスがディレクトリを指しているかどうかを取得する : os.dirExists( "path" )
import std/os if os.dirExists( "path" ): echo "path", " is dir"
ドキュメント:
dirExists