Quantcast
Channel: Flat Leon Works
Viewing all 120 articles
Browse latest View live

【Nim】個人的逆引きリファレンス

$
0
0

Nimの勉強を兼ねて逆引きリファレンスとしてまとめてみようと思います。(随時更新)

注意:

  • 使用しているNimのバージョンは0.17.0です
  • Nimに詳しいわけではないので変な書き方をしているかもしれません

Nimを普通に実行する

nim c -r -hints:off Nimソースファイルパス

プログラムに引数を渡したい場合は、Nimソースファイルパスのあとに続ける。

ドキュメント :
Compiler Usage

Nimのバージョン情報を取得する

echo NimVersion # 0.17.0

ドキュメント :
NimVersion

コマンドライン引数を取得する

import os

# コマンドライン引数を個別に取得(0番目はプログラム名)for i in0..os.paramCount():
    echo os.paramStr(i)

# まとめて取得する(0番目はプログラム名ではない)for i in os.commandLineParams():
    echo i

ドキュメント :
commandLineParams


プログラミングを学ぶこつ

$
0
0

プログラミングを学ぶこつは「慣れ」と「模索」

プログラミングにはいくつもの壁があります。この壁の多さから挫折してしまうひとが沢山いるようです。自分がプログラミングを学んできて思うのは、プログラミングの学習で重要なのは「慣れ」と「模索」なのではないかということです。

ここでいう「慣れ」とは、

  • 時間をかける
  • 何度も触れる

ということです。

また「模索」は、

  • 試行錯誤
  • 新しいことを学ぶ

ということです。

この「慣れ」「模索」でプログラミングの壁をどうやって乗り越えるのかを説明したいと思います。

【壁1】難しい用語が出てきた

プログラミングの学習をしていると新しい概念や機能が一気に出てきて全く理解できない、難しすぎると感じることがあるかもしれません。しかしこれらの概念や機能も、何回も触れていくうちに段々とわかるようになっていくものです【慣れ】。また実際に使ってみたり、あるいはその本の別の章や、別の本を読むことでも理解できるようになるかもしれません【模索】。

【壁2】文字ばかりの画面を見続けるのはもう嫌だ

プログラミングはPCの画面を見続けることになります。これが結構辛かったするのですが、これも「慣れ」で解決することができます。具体的に言うと体が慣れていきます(笑)。あるいは「模索」をすることで、テキストの背景色を暗くして目に優しくすることで対策したりするのかもしれません。

【壁3】情報量が多すぎる

プログラミングはとにかく情報量が多くなります。

プログラミング言語の情報」は使用するプログラミング言語やその作法の情報です。一般的プログラミング言語は、この情報自体で少なくとも本を1冊は書ける情報量です。「やり方の情報」はやりたいことを実現するための情報です。プログラミングにおいてやりたいことを実現する方法は通常いくつもあり、プログラミング言語の選択、API(ライブラリ)の選択、さらにその中でもどのAPIを使うのか、さらにどこを自分でコーディングするのかどうやってコーディングするのかを決めていかなくてはなりません。「自身が記述したソースコードの情報」も当然プログラミングで扱われる情報でその情報量は作業を進めるうちにどんどん増えていきます。

このような情報量の多さを処理しきれず、また取捨選択の難しさから挫折しそうになってしまうかもしれません。しかしこれも「慣れ」で解決することができます。情報量が多かったとしても、時間をかければすべてを拾うことができます。また新しい情報を得ることに慣れていくと必要な情報だけを選んで取得することもできるようになっていきます。やり方の情報が多すぎる問題は、時間をかけることでどんな選択肢があるのか全体像が見えるようになります。全体像が見えるとどれを選択すべきなのかも選びやすくなります。自身が記述したソースコードの情報量が多くて混乱してしまう場合、何度も見ているうちに理解できるようになっていきますし、プログラミングに慣れてくるとどういう書き方をすると混乱しなくなるのかもわかってきます。

【壁4】なかなか完成しない

プログラミングによるものづくりは、予想外の作業が多く発生するものです。このことでプログラミングに関して予想以上の大変さを感じ挫折しそうになってしまうかもません。しかしこれも「慣れ」です。プログラミングを続けていくうちに「そういうものだ」と慣れていくことでしょう。またプログラムの規模が大きくなるにつれ、プログラムの実装が難しくなっていくかもしれません。これは「模索」することでいろいろな手法を知り、プログラムの規模が大きくなっても破綻しないプログラミングの方法を見つけることができるかもしれません。

まとめ

「慣れ」と「模索」でプログラミングの壁を乗り越える方法を見てきましたが、これはプログラミングを初めて覚えるときに限らず、新しい技術を覚えるときにも使えるものだと思います。プログラミングはいつまでも新しいことを覚える必要があります。そういうときにも「慣れ」と「模索」を意識すると楽に新しい技術を覚えることができるかもしれません。

実はこの記事で一番伝えたかったのは、どんな技術習得も「慣れ」が重要なのではないかということです。「慣れ」とは時間をかけることで体が慣れること、あるいは問題を時間が解決してくれるということです。例えば、絵がうまくなりたいとします。そして絵を毎日描き続けていれば、必ずそのうちうまく描けるようになると思います。ここで重要なのは、どのような描き方を身につけたのかではなく、時間をかけていくことでうまい描き方を見つけることができたということです。時間が解決してくれるのです。

【Nim】個人的逆引きリファレンス

$
0
0

Nimの勉強を兼ねて逆引きリファレンスとしてまとめてみようと思います。(随時更新)

この記事は「Nim Advent Calendar 2017」の記事として登録させてもらっています。

注意:

  • 使用しているNimのバージョンは0.17.0です
  • Nimに詳しいわけではないので変な書き方をしているかもしれません
  • 個人的な解釈をしていたりするので間違っているかもしれません
  • いきなりこの逆引きリファレンスを見るのではなく、先に公式マニュアルなどを一通り読むことをおすすめします

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( """abc""" )
# 出力:# 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)と等価。# 名前付き引数proctest_proc( a:int, b:string ) =
    echo a, b
test_proc( 1, "hoge" )
test_proc( 1, b="hoge" ) # 名前付き引数

ドキュメント :
Procedures
Command invocation syntax
Method call syntax

プロシージャを定義する

proc 識別子(仮引数名:型名, 仮引数名:型名...): 戻り値型 =
    プロシージャ本体
# 普通に定義procmyProc1(name: string): bool =
    return name == "aaa"procmyProc2(name: string): bool = return name == "aaa"# 1行なら改行不要procmyProc3(name: string): void = echo( name ) # 戻り値なしprocmyProc4(name: string) = echo( name ) # 戻り値なしなら戻り値型指定は省略できるprocmyProc5(name: string): bool = name == "aaa"# 最後の式は暗黙でreturnされるprocmyProc6(name: string = "aiueo") = echo( name ) # デフォルト引数を設定可能procmyProc7(name = "aiueo") = echo( name ) # デフォルト引数を設定した場合、型指定は省略できるprocmyProc8(name: string): bool# 宣言のみ
myProc8() # この時点でプロシージャ定義がされていなくても宣言があれば呼び出せるprocmyProc8(name: string): bool = returntrue# 宣言したらプロシージャ定義は必要(ないとコンパイルエラー)procmyProc9(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 in1..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 incountup(1, 10):
    iffalse: continue# continue文が使えるiffalse: break# break文も使える
    echo i
for i in1..10: # 1..10はcountup(1,10)と同じ
    echo i
for i in1..<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 = 1while i <= 10:
    iffalse: continue# continue文が使えるiffalse: 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 in0..5:
    if i == 3: break# ブロック名を指定して抜け出すこともできるblock myBlock:
    block myBlock2:
        iftrue:
            break# myBlock2から抜け出すelse:
            break myBlock # myBlockから抜け出す

ドキュメント :
Break statement

when文を利用する

C言語における、#ifに近い機能。when文はコンパイル段階で条件分岐が行われる。そのため条件式は定数式しか受け付けない。また、ブロックスコープは発生しない。

whentrue:
    echo "aaa"var x = 10eliftrue:
    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が呼ばれるようになっている
iteratorcountup(a, b: int): int =
    var res = a
    while res <= b:
        yield res
        inc(res)
for i incountup(1, 10):
    echo i

ドキュメント :
Iterators and the for statement
Iterators(チュートリアル)

プロシージャル型を利用する

プロシージャル型はプロシージャへの参照のようなもの。プロシージャル型の変数を使うことでC言語の関数ポインタのような処理が可能になる。

proctest2(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 = intvar mi: MyInt
var mi2: MyInt2
mi = mi2 # 型名は違うけど、実際の型はintなので代入できる

ドキュメント :
Type sections

既存の型から独自の型を作る

type
    MyInt = distinctint
    MyInt2 = distinctintvar 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文走査用

ドキュメント :
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型を渡すと自動で展開されて個別の引数として渡される
    • array型のまま渡したい場合は、その値をarrayコンストラクタでさらにくくればよい
    • マニュアルにはvarargs[typed]を使えばarray型の自動展開を回避できると書いてあったが、試してみるとコンパイルエラーになった(procではなくtemplate用?)
proctest(a: varargs[string]) =
  for s in a:
    echo s
    
test( "a", "i", "u" )
# 出力:# a?# i?# u?

test( ["a", "i", "u"] ) # array型は自動展開されるので↑と同じ引数を渡したことになる# 出力:# a# i# uproctest2[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]型になる# 変換プロシージャを用意(末尾に?を追加する例)proct(x:string): string = x&"?"# 変換処理を指定proctest3(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

ドキュメント :
Tuples and object types
Tuple unpacking
Tuples(チュートリアル)

Object型を利用する

  • Object型はタプルに以下のようなオブジェクト指向プログラミングの機能を追加したようなもの
    • 型の継承
    • 非公開フィールド
    • 独自の型(暗黙でdistinctが付くイメージ?)
  • Object型名()でオブジェクトを構築できる(object construction expression)
    • 引数を渡す場合はフィールド名指定が必須
    • Object型がref付きの場合、暗黙にsystem.newが呼ばれる
  • {.final.}プラグマで派生を禁止させることができる
  • RootObj型を継承していないObject型は暗黙に{.final.}プラグマが付く。またフィールドはすべて公開される。
  • refを付けない場合、派生型を基底型へ代入すると派生型のフィールドが切り捨てられてしまうことに注意(C++でいうオブジェクトスライス問題)
  • 非公開フィールドは同じモジュール内ならアクセス可能
  • C++でいうメンバ関数というものは存在しない。ただし、メソッド記法を使うことでプロシージャをメンバ関数のように呼び出すことは可能
【Object型定義】
type# 新規Object型を定義する新規オブジェクト型名 = objectフィールド名: 型名 # 公開フィールドフィールド名: 型名 # 公開フィールド
        ...
    # 派生型定義新規オブジェクト型名 { .final. } = objectof基底型 # { .final. }は任意フィールド名*: 型名 # 公開フィールドフィールド名: 型名   # 非公開フィールド
        ...

【Object型構築】
var変数名 = オブジェクト型名(フィールド名:値,フィールド名:値,...)
type# Object型を定義
    MyObj = refobjectof RootObj
        x: int
        y: int# 派生型を定義
    MyObj2 = refobjectof MyObj
        z: intproctest(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 in0..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: refintassert x == nil# まだどこも参照していない

x = int.new# newプロシージャでtraced objectを作成し参照型変数へ代入
x[] = 10# デリファレンスは[]演算子を使う
echo x[]# 同じ参照先を指す参照型変数を定義してみるvar x2 = x

x2[] = 20# 参照先の中身を変更assert x[] == 20# xとx2は同じ参照先なのでxの参照先の中身も変更される# 引数として参照を受け取ることもできるproctest(a:refint) = echo a[]

test( x )

#[ 参照ではないのでエラー
test( 10 )
var v = 10
test( v )
]#

ドキュメント :
Reference and pointer types

無名プロシージャを利用する

【無名プロシージャ】
proc(引数リスト): 戻り値型 = 文リスト
# 無名プロシージャを作成し、変数へ代入var p = proc( x:int ): int =
  return x + 1

echo p( 10 ) # 11

ドキュメント :
Anonymous Procs

クロージャを利用する

  • モジュールトップレベル以外で定義されたプロシージャ(無名プロシージャ、名前付きプロシージャ)はクロージャとなる
  • クロージャクロージャを内包するスコープのローカル変数へアクセスできる
  • キャプチャされた変数は"環境"としてクロージャへの隠し引数として渡される
  • キャプチャされた変数は参照としてアクセスされる
  • "環境"用のメモリはヒープ上に確保されるが、可能な場合はスタック上で確保される
procgetClosure(n:string): proc() =
    var x: intvar name = n
    returnproc() =
        x = x + 1
        echo name, " : ", x
    #[ 普通の名前付きのプロシージャもプロシージャル型として渡せばクロージャになるprochoge() =
        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: ジェネリクス型名[型引数リスト]
# ジェネリクスプロシージャを定義proctest[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] = refobjectof 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

ドキュメント :
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
  • テンプレート仮引数は同名の外部の識別子(変数名など)を隠蔽する(通常のプロシージャと同じく)
【テンプレート定義】
templateテンプレート名(引数リスト): 戻り値型 = 文リスト

【テンプレート呼び出し】
テンプレート名(引数リスト) # 普通に呼び出し第一引数 テンプレート名 第二引数 # 演算子形式で呼び出し第一引数.テンプレート名(残りの引数リスト) # メソッド記法で呼び出しテンプレート名(引数リスト): # :のあとに続くブロック文は最後の引数として渡されるブロック文
# 渡されたブロック文全体を文字列として出力するコードを追加するテンプレートtemplateechoBlock( 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

macromymacro(): untyped =
    return nnkCommand.newTree(
        newIdentNode(!"echo"),
        newLit("hello")
    )

mymacro() # echo "hello"と展開される

ドキュメント :
Macros

モジュールを利用する

  • モジュールによってプログラムを分割することができる
  • モジュールは個別のファイルによって定義される(そしてファイル名がモジュール名になる)
  • モジュールごとに名前空間を持つ
  • import文を使うことで他のモジュールにアクセスすることができる
  • モジュールトップレベルに存在する*が付いたシンボルは他のモジュールからアクセスが可能になる
  • export文を使うことで、export文を使ったモジュールがインポートされたときに自動でそのモジュールもインポートされる
【import文】
importモジュール名 # 普通にインポートimportモジュール名 exceptシンボル名 # 一部のシンボルを除いてインポートimportモジュール名 as別名 # モジュールをインポートし、別名でアクセスできるようにするfromモジュール名 importシンボル名 # 一部のシンボルのみインポートする【export文】
exportモジュール名

ドキュメント :
Modules

マクロ

ドキュメント :
macrosモジュール

ASTノードを文字列化する

ASTノードを作成する

操作コード
空ノード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 newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: varargs[NimNode]): NimNode {..}
カッコ式proc newPar(exprs: 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
debugEchowriteLine

その他

Nimのバージョン情報を取得する : NimVersion

echo NimVersion # 0.17.0

ドキュメント :
NimVersion

コマンドライン引数を取得する : os.commandLineParams()

import os

# コマンドライン引数を個別に取得(0番目はプログラム名)for i in0..os.paramCount():
    echo os.paramStr(i)

# まとめて取得する(0番目はプログラム名ではない)for i in os.commandLineParams():
    echo i

ドキュメント :
commandLineParams

型を文字列化する

typetraits.nameプロシージャを使う

import typetraits

echo typetraits.name( int ) # intprocechoType[T]() = echo( typetraits.name( T ) )
procechoType[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

ドキュメント :
`type`
name

プロシージャの引数で型(型名)を受け取る

引数の型としてtypedescを指定すると、型(型名)で受け取れる。この場合、仮引数名は型を表すようになる。

import typetraits
 
procechoType(x:typedesc) =
    var hoge: x # xは型を表す
    echo typetraits.name( x ) # typetraits.nameも引数がtypedescになっているので型を渡せる
 
echoType(int) # 出力:intint.echoType  # メソッド構文も使える# typedescは型しか受け付けないので、普通の式を渡してもコンパイルエラーになる# echoType(1+2) # エラー
echoType(type(1+2)) # type演算子を使えば一応渡せる# 普通の型を受け取るプロシージャをオーバーロードすることで、どちらでも渡せるようになるprocechoType[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

macroechoTree( x:untyped ): untyped =
    echo treeRepr( x )

echoTree( 1+3 )

出力

Infix
  Ident !"+"
  IntLit 1
  IntLit 3

ドキュメント :
treeRepr

変数のアドレスをuint64型で取得する

addr演算子で取得したptr T型をuint64型へキャストすればよさそう(正しくアドレスが取れているのかどうかは不明…)

procgetAddrInt[T](x:var T): uint64 =
    cast[uint64](addr(x))

var hoge: intvar hoge2: string

echo getAddrInt( hoge )
echo getAddrInt( hoge2 )

ドキュメント :
`addr`

任意の型のデフォルト値を取得する

int型などのデフォルト値を取得する方法がわからなかったので*1、プロシージャとして作ってみました。もっと良い方法があるかもしれません。

procgetDefault(T:typedesc): T = (var temp:T;temp)
procgetDefault[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:
    discard1 + 2iftrue:
        echo "aaa"
)):
    echo "can compile"else:
    echo "can't compile"

ドキュメント :
compiles

複数の識別子を繋げて1つの識別子を作る

複数の識別子を空白で繋げて``でくくると、1つの繋がった識別子として扱われる。テンプレートでこの機能を使うことで新しい識別子を作ることができる。これはC言語マクロでいうところの##と同じような機能。

templatejoin( 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

*1:int()でデフォルト値取得できると思ったのですがコンパイルエラーになりました。

NimとC++を比較してみる

$
0
0

この記事は「Nim Advent Calendar 2017」の記事として登録させてもらっています。

NimはNimソースコードC言語ソースへ変換するトランスコンパイラ言語です。これは普段C++を使っている人にとって非常に重要なことです。なぜなら、C++(C言語)が使える環境なら基本的にはどこでもNimを使うことができるからです*1。もちろん、普段C++を使っている環境でNimを使うにはメモリ管理やC++側との連携についてなど考慮すべきことはいろいろあると思いますが、やはりC言語ソースコードが生成されるというのは大きなメリットです。普段C++C言語を使っている人が現実的な選択肢としてNimの利用を考えることができるわけですから。というわけで、この記事ではC++とNimの書き方の違い、機能の違いなどを紹介したいと思います。

Nimの言語機能についての詳細は公式の Nim Manualを見てください。

ノート:

  • 筆者はまだNim初心者なので間違っていたりもっと良い方法があったりするかもしれません。
  • C++11以降についても詳しくないので間違っているかもしれません。
  • C++とNimの比較についてはNimのwikiにもページがあります。 ->Nim for C programmers
  • 随時更新予定

コメントアウト

Nimは複数行コメントがネスト可能な点が嬉しい。#if 0的な用途としてwhen 0:も使えなくはないがパースはされるのでコンパイルエラーが発生する場合がある。

【Nim】
# 1行コメント#[複数行コメント
    #[ネスト可能
    ]#
]#when0:
    コメントアウトとして利用できなくもない( ただし、インデントが必要な上にパース可能なものじゃないとコンパイルエラーが発生してしまう )
【C++】
// 1行コメント/*複数行コメント/*ネストはできない    */*/#if 0プリプロセッサをコメントアウトに使うこともできる(if 0を1と書き換えるだけでコメントアウトを切り替えられるので楽)#endif

Nimは文末にセミコロンが不要。セミコロンを使うこともできるので、1行に複数の文を記述することも可能。文末にセミコロンをつけるのとてもわずらわしいのでNimのこの仕様は嬉しい。というかモダンな言語では当然の仕様。

【Nim】
echo( "aaa" )
echo( "bbb" ); echo( "ccc" )
【C++】
puts( "aaa" );
puts( "bbb" ); puts( "ccc" );

変数

NimはC++と違い、変数が自動で初期化されるのが嬉しい。型推論はどちらもある(ただしC++型推論C++11以降から)

【Nim】
var x: int# 0で初期化されるvar y: int = 10var z = 20# 型推論を利用
【C++】
int x; // 未初期化になってしまうint y = 10;
auto z = 20; // ※C++11以降のみ

関数

Nimは関数ではなくプロシージャという名称だがここでは関数として統一。

Nimの関数で嬉しいのはreturn文を省略できること。return文はプログラミングをしていると何度も書くことになるので、それを省略できるのはとても大きい。もう1つ大きな特徴はNimにはメンバ関数という概念が存在しないこと。すべて関数なので、staticメンバ関数にするか関数にするか迷うことがない。そしてすべての関数でメソッド構文*2が使えるので、実質的にどんな型に対しても自由にメンバ関数を追加できるようになっている。

【Nim】
prochoge( a:int, b:int =10, c=20 ): int =
    a + b + c # 最後の式は暗黙に返却されるprochoge2( a:int, b:int =10, c=20 ): int =
    result = a + b + c # 暗黙のresult変数が利用可能return result
prochoge3() =procinner() = # 関数やブロック内部に関数を定義することが可能
        echo "inner"
    inner()
【C++】
int hoge( int a, int b=10, auto c=20 ) {
    return a + b + c
}
int hoge2( int a, int b=10, auto c=20 ) {
    int result = a + b + c
    return result
}
void hoge3() {
    /* 内部関数は定義出来ない。   void inner() {            puts( "inner" );   }*/// ただし内部でクラスは定義できるのでstaticメンバ関数を実質内部関数として利用できなくもない。struct temp{
            staticvoid inner() {
                puts( "inner" );
            }
        };
        temp::inner();
}
機能C++Nim備考
インライン化ありあり【Nim】inlineプラグマを使う
デフォルト引数ありあり
引数のconst参照渡しありあり【Nim】Nimは引数の渡し方は実質的にconst参照渡しになっている*3。ただし、ref型の場合は変更可能となってしまう*4
引数の非const参照渡しありあり【Nim】varを指定すれば変更可能になるので、実質的に非const参照渡しになる。
オーバーロードありあり
演算子オーバーロードありあり
ユーザー定義演算子なしあり
ローカル関数(関数のネスト)なしあり【Nim】関数内だけでなくブロック内でも定義可能
C++】ローカルクラスのstaticメンバ関数をローカル関数として利用できなくもない
メンバ関数ありなし【Nim】Nimではメンバ関数が存在しない。ただしメソッド構文でメンバ関数のように呼び出すことが可能になっている。この仕様はメンバ関数が持つ諸問題*5を解決するのでとてもスマートだと思う。
constメンバ関数ありなし【Nim】近い指定としてnoSideEffectプラグマがあるが、メンバ変数(フィールド)だけでなくすべての変数に対して変更のない、つまり副作用のない関数になる。
副作用のない関数(純粋関数)なしあり【Nim】noSideEffectプラグマを使う
動的ディスパッチ
(仮想関数)
ありあり【Nim】procではなくmethodを使う
C++メンバ関数にvirtualを付ける
ダブルディスパッチなしあり【Nim】Nimのmethodは第一引数だけでなくすべての引数の型からディスパッチを行うので多重ディスパッチが可能になっている
C++ダブルディスパッチイディオムを使えば可能
純粋仮想関数ありなし【Nim】メンバ関数の実装を強制するという意味ではconcept機能もしくはcompiles関数を使えば実現可能…?
関数ポインタありあり【Nim】Nimではプロシージャル型という名称
関数宣言(関数プロトタイプ)ありあり
クロージャ(無名関数)ありあり【Nim】ローカル関数(ネストされた関数)はすべてクロージャ
C++ラムダ式という名称。(C++11以降の機能)
暗黙のreturnなしあり【Nim】最後の式が暗黙にreturnされる
暗黙のresult変数なしあり【Nim】戻り値型のresult変数が暗黙に定義される。result変数は暗黙にreturnされる
関数テンプレートありあり【Nim】Nimではジェネリクスという名称
関数テンプレート引数の推定*6ありあり
関数テンプレートの特殊化あり不明【Nim】調査不足です…。is演算子コンパイル時に分岐とか…?
コンパイル時関数(定数式用関数)ありあり【Nim】コンパイル時に計算可能なら自動でコンパイル時関数になる。(明示的に定義したい場合はcompileTimeプラグマを使う)
C++】constexpr を付ける。(C++11以降の機能)
戻り値の型推論ありあり【Nim】戻り値型としてautoを指定
C++】戻り値型としてautoを指定。(C++14以降の機能)
コルーチン(中断再開が可能な関数)なしあり【Nim】procではなくiteratorとして関数を定義する
C++】boostにはあるらしい
型を引数として受け取るなしあり【Nim】引数の型としてtypedescを指定することで、型を引数として受け取ることが可能(実態としてはジェネリクス関数の糖衣構文)
非推奨指定ありあり【Nim】deprecatedプラグマを使う
C++】deprecated属性を使う(C++14からの機能)
SFINAEありなし【Nim】SFINAEは無いがcompiles関数(任意のコードについてコンパイル可能かどうかをbool値で取得できる関数)+when文でそれ以上に強力なことが可能 ->コード例
関数内static変数ありあり【Nim】関数内の変数にglobalプラグマを付ける
可変個引数ありあり【Nim】引数の型としてvarargsを使う
C++】 ... を使う

関数呼び出し

Nimは関数の呼び出し時の()を省略することができる。またメソッド記法を使うことですべての関数を第一引数.関数名(...)の形式で呼ぶことができる。

【Nim】
hoge( 10 )
hoge 10# ()を省略できる10.hoge() # メソッド構文でオブジェクト指向風に記述可能10.hoge # メソッド構文でも()が省略できる

hoge( 10, b=7 ) # 名前付き引数も可能
【C++】
hoge( 10 )

#if 0 // 以下のような呼び出し方はできないhoge 1010.hoge()10.hogehoge( 10, b=7 )#endif

制御構造

機能C++Nim備考
if文ありあり【Nim】if:elif:else:という形式
コンパイル時if文ありあり【Nim】when文を使う
C++】#ifを使う
switch文ありあり【Nim】Nimではswitch-caseではなくcase-ofという文法になっている
for文(カウンタ方式)ありなし【Nim】`0..10`というようなレンジ記法を使うことでカウンタ式のfor文とほぼ同じことが可能
for文(イテレータ方式)ありありC++C++11以降の機能
while文ありあり
break文ありあり
ラベル付きbreak文なしあり【Nim】ブロックにラベルを付けることが可能で、そのラベルを指定してスコープを抜けることが可能。(多重for文からの脱出などで使う)
continue文ありあり
goto文ありなし
if式ありあり【Nim】if文をそのまま式として利用可能
C++三項演算子(条件演算子)を使う
コンパイル時if式なしあり【Nim】when文をそのまま式として利用可能
switch式なしあり【Nim】case文をそのまま式として利用可能

組み込み型とその他の型

C++Nim備考
voidvoid
真偽boolboolC++、Nimともに真偽値は、true, false を使用
整数intint整数型リスト
【Nim】int,int8,int16,int32,int64,
uint,uint8,uint16,uint32,uint64
C++】int8_t,int16_t,int32_t,int64_t,
unsigned int,uint8_t,uint16_t,uint32_t,uint64_t
浮動小数点数floatfloat浮動小数点数型リスト
【Nim】float32,float64
C++】float,double
文字charchar
文字列(低レベル)char*cstring
文字列std::stringstring
固定長配列[](配列)array
動的配列std::vectorseq
リストstd::listSinglyLinkedList
DoublyLinkedList
【Nim】listsモジュールで提供
列挙型enumenum【Nim】enum値の文字列表現が言語の機能として備わっている。これはとても嬉しい。
構造体/クラスstruct/classobject【Nim】何も継承していないobjectはC++でいう構造体のようにすべてのメンバ変数が公開される
タプルstd::tupletupleC++C++11以降
セット(集合)std::setset【Nim】型のサイズが2バイト以内という制限あり
マップ(辞書)std::map
std::unordered_map
OrderedTable
Table
【Nim】tablesモジュールで提供
C++】std::unordered_mapはC++11以降
参照&ref【Nim】refはGC対象になる
ポインタ*ptr
関数ポインタ関数ポインタプロシージャル型
型の別名ありあり【Nim】type 別名 = 型
C++】typedef 型 別名;
独自型なしあり【Nim】type 独自型名 = distinct 型
スマートポインタありなし【Nim】Nimは言語レベルでGCをサポートしているのでスマートポインタは不要
C++】unique_ptr, shared_ptrなどがある(C++11以降)
弱参照ありなし【Nim】なし。欲しい。Nim作者による微妙な実装例はあったが…。->weakrefs.nim
C++】weak_ptrがある(C++11以降)

モジュール(ファイル分割)

C++にはモジュールという概念はないが、ここではNimとの比較のために外部ファイルから利用できる形をモジュールと呼ぶことにする。

C++Nim備考
モジュール化宣言と実装をそれぞれ別ファイルにする別ファイルにする
モジュールのインポート#includeimport文
単純な別ファイルの取り込み#includeinclude文

こうして表にしてみるとたいしたことないように見えるが、宣言と実装を別ファイルにする必要がないというのは大きなメリット。

名前空間

Nimには明示的に名前空間を指定する文法は存在せず、モジュールがそれぞれ独立した名前空間となる。複数のファイルに同じ名前空間を与えたい場合は、その名前空間用のファイルを用意しその中でexport文を使って複数のファイルをインポートさせるようにすれば可能かもしれない…。(要検証)

クラス(オブジェクト指向プログラミング)

Nimにはクラスという概念は存在しないが、object型を使い独自の型を定義することでクラスのように扱うことができる。比較をわかりやすくするため、ここではNimのobject型をクラスと呼ぶことにする。

C++Nim備考
クラス定義ありあり【Nim】type クラス名 = ref object of RootObj
C++】class クラス名{};
継承ありあり
多重継承ありなし
派生禁止ありあり【Nim】finalプラグマを使う
C++】finalを使う(C++11以降の機能)
抽象クラス(派生が必要なクラス)ありなし【Nim】特定のメソッドを持ったクラスを扱いたい場合はconcept機能が使えるかもしれない。
メンバ変数ありあり
メンバ関数ありなし【Nim】Nimにはメンバ関数の概念がない。そのかわりに関数をメンバ関数のように呼べるようになっている。
メンバへのアクセス制御ありあり【Nim】モジュール外への公開/非公開という制御のみ可能
C++】クラス外または継承クラス、フレンドクラスへの公開/非公開が制御可能
オーバーライド(動的ディスパッチ)ありあり【Nim】methodを使う
C++】virtualを使う
クラステンプレートありあり【Nim】Nimではジェネリクスという名称
コンストラクタありあり【Nim】ただし、ユーザー独自のコンストラクタは定義できない
デストラクタありあり【Nim】ただし、GC対象オブジェクトはデストラクタが呼ばれない
メンバ変数のビットサイズ指定ありあり【Nim】bitsizeプラグマを使用
C++】ビットフィールドを使用
構造体定義ありあり【Nim】何も継承していないクラスはすべてのメンバ変数が公開扱いになり、C++でいう構造体のような扱いになる
C++】struct クラス名{};

コンパイル時処理

ASTをいじれるNimのマクロはとんでもなく強力ですが、引数として渡せるのはASTとして表現可能なコードのみなので、そういう意味ではなんでも渡せるC++のマクロもやはり強力。Nimはコンパイル時に外部プロセスを実行してその出力を受け取ることができるので、コンパイル時処理に関してはもうなんでもあり感がある。

C++Nim備考
ソースコードのテキスト置換ありなし【Nim】マクロやテンプレートはテキストの操作ではなくASTの操作
C++プリプロセッサを使用する
ソースコードのAST置換なしあり【Nim】テンプレートを使う
コンパイル時計算によるAST構築なしあり【Nim】マクロを使う
ソースコードへのファイル展開ありあり【Nim】include文を使う
C++】#includeを使う
コンパイル時計算(定数式)ありあり【Nim】普通に記述可能。ただしコンパイル時計算できなければエラーになる。staticブロックや関数にcompileTimeプラグマを指定することでコンパイル時のみの処理を明示的に指定することが可能。
C++】関数や変数にconstexprを付ける(C++11以降の機能)
コンパイル時計算でのファイルロードなしあり【Nim】staticRead関数を使う
コンパイル時計算での外部プロセス起動なしあり【Nim】staticExec関数を使う

その他

C++Nim備考
ユニコード対応なしなし
(サポートモジュールはある)
【Nim】文字列はバイト列として扱われるので、utf-8ならそのまま扱うことが可能だが、文字数などは正しく取得することができない。ただし、unicodeモジュールにはutf-8文字列の文字数を取得する関数などが用意されいる。Nimはソースコード文字コードutf-8であることを前提としているので文字列リテラルとしてそのままutf-8の文字を使用することは可能。
C++】Nimと同じく、std::string,strlenなどは文字列をバイト列とみなすのでutf-8文字列の場合正しい文字数は取得できない。文字列リテラルにu8プレフィックスを付けることでutf-8文字列として扱うことが可能。ただしu8プレフィックスはC++11以降の機能。
static assertありあり【Nim】static: assert( 条件 )
C++】static_assert( 条件, メッセージ ); C++11以降
現在のファイル名や行数を取得ありあり【Nim】instantiationInfo関数を使う。ただしtemplate内でしか使えない(ラップすればよい)
C++】__FILE__、__LINE__マクロを使う

*1:資源が乏しい組み込み系などではNim採用が難しい、あるいは不可能な場合もあるかもれませんが…

*2:第一引数.関数名(第二引数..)という形で関数を呼ぶ構文

*3:Nimは仮引数を変更することができない。そして、object型などの非プリミティブ型は参照(ポインタ)で渡されるため、実質的にC++でいうconst参照渡しで引数が渡される。

*4:ref型の場合、ref型のconst参照渡しとなるため、そのref型の参照先については変更可能となってしまう。C++でのポインタ(非const)のconst参照渡しではポインタの指す先は変更可能なのと同じ原理。

*5:ある関数がどのクラスに属するべきなのかという問題や、クラス実装者以外はメンバ関数を追加できないという問題

*6:関数の実引数からテンプレート引数を推定してくれる機能

【Nim】文字列操作

$
0
0

Nimでの文字列操作関連をまとめてみました。

Nimのバージョンは 0.17.2 を想定しています。( 執筆時の最新バージョン )

文字列の基本

文字、文字列リテラル

記法名称備考
'a'文字リテラル(Character literal)
"abc"文字列リテラル(String literal)
r"abc"Raw文字列リテラル(Raw string literal)エスケープシーケンス無効
"""abc"""3重クオート文字列リテラル(Triple quoted string literal)エスケープシーケンス無効
改行可能
"をそのまま使用可能

文字、文字列型

説明
char文字を表す。C言語のcharと同じく1バイト。
string通常はこのstring型を使う
cstringC言語文字列を表す。cstringとstringは相互に変換が可能になっている。

文字列関連モジュール

標準モジュール(system,strutils)の文字列関連プロシージャ

プロシージャの説明は推測で書いている部分もあるので、もしかしたら間違っているかもしれません。

判定

プロシージャ引数:戻り値説明モジュール
`==`(x, y: string): bool文字列が一致するかどうかsystem
isNil(x: string): boolnilかどうかを取得
(stringは初期値がnil)
system
contains(s: string; c: char): bool

(s, sub: string): bool

(s: string; chars: set[char]): bool
含まれるかどうかを取得するstrutils
isAlphaAscii(c: char): bool文字列がa-z,A-Zの範囲のみかどうかを取得するstrutils
isAlphaNumeric(c: char): bool文字列がa-z,A-Z,0-9の範囲のみかどうかを取得するstrutils
isDigit(c: char): bool文字列が0-9の範囲のみかどうかを取得するstrutils
isSpaceAscii(c: char): bool文字列が空白のみかどうかを取得するstrutils
isLowerAscii(c: char): bool小文字のみかどうかを取得するstrutils
isUpperAscii(c: char): bool大文字のみかどうかを取得するstrutils
isNilOrEmpty(s: string): boolnilもしくは空文字かどうかを取得するstrutils
isNilOrWhitespace(s: string): boolnilもしくは空白のみかどうかを取得するstrutils
startsWith(s, prefix: string): bool

(s: string; prefix: char): bool
指定の文字列で始まっているかどうかを取得するstrutils
endsWith(s, suffix: string): bool

(s: string; suffix: char): bool
指定の文字列で終わっているかどうかを取得するstrutils
continuesWith(s, substr: string; start: Natural): bool指定の位置から指定の文字列で始まっているかどうかを取得するstrutils
allCharsInSet(s: string; theSet: set[char]): bool文字列内のすべての文字が指定の文字セットに含まれているかどうかstrutils
validIdentifier(s: string): bool識別子として正当化どうかを取得するstrutils

比較

プロシージャ引数:戻り値説明モジュール
`<=`(x, y: string): bool文字列の比較system
`<`(x, y: string): bool文字列の比較system
cmp(x, y: string): int文字列の比較system
cmpIgnoreCase(a, b: string): int大文字小文字を無視した比較strutils
cmpIgnoreStyle(a, b: string): int文字列を正規化した上で比較strutils
editDistance(a, b: string): int2つの文字列の編集距離(レーベンシュタイン距離)を取得するstrutils

検索

プロシージャ引数:戻り値説明モジュール
count(s: string; sub: string; overlapping: bool = false): int

(s: string; sub: char): int

(s: string; subs: set[char]): int
出現回数を取得するstrutils
countLines(s: string): int行数を取得するstrutils
find(s, sub: string; start: Natural = 0; last: Natural = 0): int

(s: string; sub: char; start: Natural = 0; last: Natural = 0): int

(s: string; chars: set[char]; start: Natural = 0; last: Natural = 0): int
指定の文字の位置を検索するstrutils
rfind(s, sub: string; start: int = - 1): int

(s: string; sub: char; start: int = - 1): int

(s: string; chars: set[char]; start: int = - 1): int
指定の文字の位置を検索する(末尾から検索する)strutils
abbrev(s: string; possibilities: openArray[string]): int指定の文字列リストの中から一致するもののインデックスを取得する(略語展開?)strutils

情報取得

プロシージャ引数:戻り値説明モジュール
len(x: string): int文字列の長さを取得system
low(x: string): int先頭インデックスを取得(たぶん常に0)system
high(x: string): int末尾インデックスを取得(空文字なら-1)system

非文字列化(string -> XXX)

プロシージャ引数:戻り値説明モジュール
parseInt(s: string): int文字列をint化するstrutils
parseBiggestInt(s: string): BiggestInt文字列をint化するstrutils
parseUInt(s: string): uint文字列をuint化するstrutils
parseBiggestUInt(s: string): BiggestUInt文字列をuint化するstrutils
parseFloat(s: string): float文字列をfloat化するstrutils
parseOctInt(s: string): int8進数文字列をint化するstrutils
parseHexInt(s: string): int16進数文字列をint化するstrutils
parseBool(s: string): bool文字列をbool化するstrutils
parseEnum[T: enum](s: string): T

[T: enum](s: string; default: T): T
文字列をenum値化するstrutils

文字列化(XXX -> string)

プロシージャ引数:戻り値説明モジュール
repr[T](x: T): stringオブジェクトの文字列表現を取得system
`$`(x: int): stringオブジェクトの文字列表現を取得system
toHex(x: BiggestInt; len: Positive): string16進数表現文字列を取得するstrutils
toOctal(c: char): string8進数表現文字列を取得するstrutils
toOct(x: BiggestInt; len: Positive): string8進数表現文字列を取得するstrutils
toBin(x: BiggestInt; len: Positive): string2進数表現文字列を取得するstrutils
intToStr(x: int; minchars: Positive = 1): stringint型を文字列化するstrutils
formatBiggestFloat(f: BiggestFloat; format: FloatFormatMode = ffDefault; precision: range[0 .. 32] = 16; decimalSep = '.'): stringfloat型を文字列化するstrutils
formatFloat(f: float; format: FloatFormatMode = ffDefault; precision: range[0 .. 32] = 16; decimalSep = '.'): stringfloat型を文字列化するstrutils
formatSize(bytes: int64; decimalSep = '.'; prefix = bpIEC; includeSpace = false): string数値のデータサイズ表現文字列を取得するstrutils
formatEng(f: BiggestFloat; precision: range[0 .. 32] = 10; trim: bool = true; siPrefix: bool = false; unit: string = nil; decimalSep = '.'): string数値の工学表記文字列を取得するstrutils

新規string作成

プロシージャ引数:戻り値説明モジュール
newString(len: Natural): string長さ指定で新規作成system
newStringOfCap(cap: Natural): stringキャパシティ指定で新規作成system

複製

プロシージャ引数:戻り値説明モジュール
copy(s: string; first = 0): string

(s: string; first, last: int): string
文字列を複製system
substr(s: string; first = 0): string

(s: string; first, last: int): string
文字列の一部分を複製system
`[]`(s: string; x: Slice[int]): string文字列の一部分を複製system

合成

プロシージャ引数:戻り値説明モジュール
`&`(x, y: string): string

(x: char; y: string): string

(x: string; y: char): string

(x, y: char): string
連結で新規作成system
`&=`(x: var string; y: string)文字列を末尾に追加system
add(x: var string; y: char)

(x: var string; y: string)

(x: var string; y: cstring)

(result: var string; x: int64)

(result: var string; x: float)
文字列を末尾に追加system
safeAdd(x: var string; y: char)

(x: var string; y: string)
文字列を末尾に追加system
insert(x: var string; item: string; i = 0.Natural)文字列を指定のインデックスに挿入system
`[]=`(s: var string; x: Slice[int]; b: string)文字列を指定の範囲に挿入system
repeat(c: char; count: Natural): string文字を指定回数連続させた文字列を取得するstrutils
repeat(s: string; n: Natural): string文字列を指定回数連続させた文字列を取得するstrutils
join(a: openArray[string]; sep: string = ""): string文字列リストを連結するstrutils

整形

プロシージャ引数:戻り値説明モジュール
strip(s: string; leading = true; trailing = true; chars: set[char] = Whitespace): string先頭と末尾の空白を除去した文字列を取得する。引数で先頭のみ末尾のみ、空白以外の文字を指定ができるstrutils
align(s: string; count: Natural; padding = ''): string文字寄せを行った文字列を取得するstrutils
wordWrap(s: string; maxLineWidth = 80; splitLongWords = true; seps: set[char] = Whitespace; newLine = "\x0D\x0A"): stringワードラップを行った文字列を取得するstrutils
indent(s: string; count: Natural; padding: string = ""): stringインデントを行った文字列を取得するstrutils
unindent(s: string; count: Natural; padding: string = ""): stringインデント解除を行った文字列を取得するstrutils
center(s: string; width: int; fillChar: char = ''): string文字列の中央寄せを行うstrutils
insertSep(s: string; sep = '_'; digits = 3): string数字文字列にセパレータを追加した文字列を取得するstrutils
escape(s: string; prefix = "\""; suffix = "\""): stringエスケープ文字をエスケープした文字列を取得するstrutils
unescape(s: string; prefix = "\""; suffix = "\""): stringescapeの逆strutils
trimZeros(x: var string)末尾の0を除去するstrutils
addSep(dest: var string; sep = ", "; startLen: Natural = 0)指定の長さ以降にセパレータ文字列を追加するstrutils

置換

プロシージャ引数:戻り値説明モジュール
replace(s, sub: string; by = ""): string置換した文字列を取得するstrutils
replaceWord(s, sub: string; by = ""): string単語置換した文字列を取得するstrutils
multiReplace(s: string; replacements: varargs[(string, string)]): string 複数置換に特化したreplacestrutils
toLowerAscii(c: char): char大文字を小文字化したものを取得strutils
toUpperAscii(c: char): char小文字を大文字化したものを取得strutils
capitalizeAscii(s: string): string最初の文字を大文字化したものを取得strutils
normalize(s: string): string正規化したものを取得。正規化とは具体的には小文字化し_を除去する。(Nimの識別子ルールで利用される)strutils
`%`(formatstr: string; a: openArray[string]): string

(formatstr, a: string): string
書式文字列を作成するstrutils
format(formatstr: string; a: varargs[string, `$`]): stringstrutils
addf(s: var string; formatstr: string; a: varargs[string, `$`])書式で文字列を追加するstrutils

削除

プロシージャ引数:戻り値説明モジュール
delete(s: var string; first, last: int)指定の範囲を削除するstrutils
removeSuffix(s: var string; chars: set[char] = Newlines)指定の末尾の文字列を除去するstrutils

分割

プロシージャ引数:戻り値説明モジュール
splitWhitespace(s: string): seq[string]文字列を空白で分割したものを取得するstrutils
[iterator] splitWhitespace(s: string): string文字列を空白で分割したものを取得するstrutils
splitLines(s: string): seq[string]文字列を行ごとに分割したものを取得するstrutils
[iterator] splitLines(s: string): string文字列を行ごとに分割したものを取得するstrutils
split(s: string; seps: set[char] = Whitespace; maxsplit: int = - 1): seq[string]

(s: string; sep: char; maxsplit: int = - 1): seq[string]

(s: string; sep: string; maxsplit: int = - 1): seq[string]
文字列を任意の文字(列)で分割したものを取得するstrutils
[iterator] split(s: string; seps: set[char] = Whitespace; maxsplit: int = - 1): string

(s: string; sep: char; maxsplit: int = - 1): string

(s: string; sep: string; maxsplit: int = - 1): string
文字列を任意の文字(列)で分割したものを取得するstrutils
rsplit(s: string; seps: set[char] = Whitespace; maxsplit: int = - 1): seq[string]

(s: string; sep: char; maxsplit: int = - 1): seq[string]

(s: string; sep: string; maxsplit: int = - 1): seq[string]
文字列を任意の文字(列)で分割したものを取得する(末尾から分割していく)strutils
[iterator] rsplit(s: string; seps: set[char] = Whitespace; maxsplit: int = - 1): string

(s: string; sep: char; maxsplit: int = - 1): string

(s: string; sep: string; maxsplit: int = - 1; keepSeparators: bool = false): string
文字列を任意の文字(列)で分割したものを取得する(末尾から分割していく)strutils

その他

プロシージャ引数:戻り値説明モジュール
setLen(s: var string; newlen: Natural)文字列の長さを変更system
cstringArrayToSeq(a: cstringArray; len: Natural): seq[string]
(a: cstringArray): seq[string]
system
allocCStringArray(a: openArray[string]): cstringArraysystem
deallocCStringArray(a: cstringArray)system
shallow(s: var string)代入時の動作を浅いコピーにするsystem
GC_ref(x: string)参照カウントを増やすsystem
GC_unref(x: string)参照カウントを減らすsystem
getRefcount(x: string): int参照カウントを取得するsystem

リンク

【Nim】seq型とstring型のリファレンスの罠

$
0
0

[執筆時のNimバージョン : 0.17.2]

seq型とstring型のリファレンスの罠に引っかかったので、記事に残しておこうと思います。

罠を紹介するために事前知識として「seq型とstring型のデフォルト値」と「リファレンスのデフォルト値」について説明します。

まず、seq型とstring型のデフォルト値についてです。Nimのseq型とstring型はデフォルト値がnilとなっています。そのため、実体を作るまで要素の追加などの操作ができません。seq型やstring型を利用するにはまず実体を作る必要があります。

var s: string

s.add( "aaa" ) # 中身がnilなのでエラー

s = ""# 実体を用意
s = newString(0) # これでもよい

s.add( "aaa" ) # OK

次にリファレンスについてです。リファレンスも同じようにデフォルト値がnilなので、アクセスするにはnewプロシージャでインスタンスを作る必要があります。

var i: refint

i.new# インスタンス作成

i[] = 10# []はデリファレンス演算子

問題なのはseq型やstring型のリファレンス(ref)を利用する場合です。newプロシージャでseq型やstring型のインスタンスを作っても、 まだその中身はnilなのです。なぜなら先ほど説明したように、seq型やstring型のデフォルト値がnilだからです。

{.experimental.} # 自動デリファレンスに必要var s: refstring

s.new# string型インスタンスを作成

s.add( "aaa" ) # エラー!インスタンスを作ったのになぜ!

なので、seq型やstring型のリファレンスを扱う場合は、まずseq型やstring型自体のインスタンスを作り、さらにその中身を作成する必要があります。

{.experimental.} # 自動デリファレンスに必要var s: refseq[int]

s.new# seq型インスタンスを作成
s.newseq( 0 ) # seq型インスタンスの内部的なインスタンスを作成

s.add( 1 ) # アクセス可能

これがseq型とstring型のリファレンスの利用する際の罠です。seq型とstring型のデフォルト値がnilなことを考えれば、そのリファレンスを扱う場合に2回インスタンスを作成する必要があるのは当然のことなのですが、結構はまりやすい罠だと思うので紹介しました。

【Nim】クラス定義マクロを作ってみる

$
0
0

Nimにはクラスという概念がありません。しかし、オブジェクト指向プログラミング自体はサポートされており、object型を使えばクラスとほぼ同じことが可能になっています。ただし、object型の定義が微妙に面倒なことに加え、やはりクラスが欲しいということで、簡潔にクラス(object型)を定義できるマクロを作ってみました。

注意

  • とりあえず作ってみたレベルなので、実際に使ってみると何か問題が出てくるかもしれません
  • 執筆時のNimバージョン : 0.17.2

クラス定義マクロ

できあがったクラス定義マクロを使ったコードがこちらです。

# クラス定義
class Hoge:
    # クラススコープの型定義 (Hoge.Flagsなどでアクセス可能)type Flags = enum On, Off
    type MyInt = int# メンバ変数定義var x: intvar y: int# メンバ関数定義(第一引数としてself:Hogeが挿入される)proctest(a:int,y:int):bool =
        echo "Hoge.x=", self.x
        returnfalse

今回作ったクラス定義マクロは以下のような特徴があります

  • 通常の方法でobject型を定義するより簡素な記述方法で定義可能
  • メンバ関数の定義が可能
  • クラススコープの型定義が可能

それぞれ紹介します。

簡素な記述

まず、マクロを使わないで普通にobject型を定義する方法を紹介します。

type Hoge = refobjectof RootObj
    x: int
    y: int

問題はref object of RootObjです。1つのobject型を定義するのにこれを毎回入力するのはかなり面倒です。いちおう、type hoge = objectという記述でもobject型を定義できるのですが、派生できない、代入時にコピーされてしまう、デストラクタが定義できない、などいろいろな挙動が変わってしまうので、Nimではref object of RootObjが基本的なobject型の定義方法だと思っています。

これをclassマクロを使えば以下のように簡潔に記述できます。

class Hoge:
    var x: intvar y: int

基底クラスを指定したい場合は以下のように2通りの記述方法が利用できます。

class Hoge of Base:
    var x: intvar y: int

class Hoge(Base):
    var x: intvar y: int

メンバ関数の定義が可能

Nimのobject型にはメンバ関数という概念はありません。そのかわりにメソッド構文というプロシージャをメンバ関数のように呼び出すことができる構文があります。この構文は第一引数.プロシージャ名(第二引数...)というような記述方法になります。なのでNimでメンバ関数相当のものを定義するには、第一引数でそのクラスのインスタンスを受け取るようにします。

proctest(x:Hoge) = discard# Hoge型.test()でアクセスできる -> つまり、Hoge型のメンバ関数として扱える

classマクロでは、第一引数にクラス型引数を自動で挿入することで楽にメンバ関数(相当のもの)を定義できるようにしています。また、classブロック内に記述することになるので、メンバ関数ということがよりわかりやすくなります。

class Hoge:
    var x: intvar y: intproctest() = discard# マクロによって以下のように第一引数にself:Hogeが追加される# proc test(self:Hoge) = discard

クラススコープの型定義が可能

C++ではクラス定義内でtypedefやenum定義をするとそのクラスに属するものとして扱われます。つまりクラススコープというものが存在します。Nimにはこのようなものが存在しません*1。ですが、templateを使うことで似たようなものを実現することができます。

type Hoge = refobjectof RootObj
    x: int
    y: inttemplateFlags(T:typedesc[Hoge]) : untyped = `T Flags` # Hoge.Flags を HogeFlagsへと変換するtemplatetype HogeFlags = enum On, Off # Hoge.FlagsはHogeFlagsへと変換されるのでHogeFlagsという名前にしておくvar f: Hoge.Flags # クラススコープのようなものを実現

イデアとしては、メソッド記法とtemplateの識別子結合機能(Identifier construction)を使って、クラス名.型名というコードをクラス名型名に変換するというものです。これによってクラス名.型名という記述方法が可能になり、利用者側からみたらクラススコープとして扱うことができるようになります。型の定義側は、型名の頭にクラス名を付けることと、クラス名.型名というアクセスを可能にするためのtemplateを定義する必要があります。classマクロはこの作業を自動化します。

class Hoge:
    x: int
    y: inttype Flags = enum On, Off # クラススコープで型(enum)定義# classマクロによりFlagsはHogeFlagsという型名になる# また、Hoge.Flagsでのアクセスを可能にするためのtemplateが自動で挿入されるvar f: Hoge.Flags # Hoge.Flagsでアクセス可能

classマクロによるコード展開例

冒頭のclassマクロを使ったコード例はclassマクロによって以下のように展開されます*2

type Hoge = refobjectof RootObj
    x: int
    y: inttype HogeFlags = enum On, Off
type HogeMyInt = intproctest(self:Hoge,a:int,y:int):bool =
    echo "Hoge.x=", self.x
    returnfalsetemplateFlags(T:typedesc[Hoge]) : untyped = `T Flags`
templateMyInt(T:typedesc[Hoge]) : untyped = `T MyInt`

classマクロ実装コード

とくにライセンスとかは設定しないので、ご自由にお使いください。ただし動作保証はしません。

{.experimental.}

import typetraits
import macros
import strutils

type TraverseOp = enum Continue, Break, SkipChild, SkipSibling, Finished
proctraverse(n: NimNode; action:proc(n:NimNode;parents:refseq[NimNode]):TraverseOp; parents:refseq[NimNode] = nil ):TraverseOp {.discardable.} =
    var parents = parents
    if parents == nil:
        parents.new
        parents.newseq( 0 )
    parents.add( n )
    defer:
        echo "discard parents.pop()"discard parents.pop()
        assert( false ) # Nimコンパイラのバグでdeferブロックが実行されない(コンパイル時処理のみっぽい?)for it in n.children:
        case action( it, parents )
        of Continue: discardof Break: return Break
        of SkipSibling: return SkipSibling
        of SkipChild: continueelse: assertfalsecase traverse( it, action, parents )
        of Break: return Break
        else: discardreturn Finished

procfindNode(n:NimNode,kind:NimNodeKind):NimNode=
    var ret: NimNode
    n.traverse do (n:NimNode;parents:refseq[NimNode]) -> TraverseOp:
        if n.kind == kind:
            ret = n
            return Break
    result = ret

procconvertToMemberProc( procDefNode:NimNode, className:string ) =
    ## プロシージャをメンバ関数化する -> 第一引数に指定の型のselfパラメータを追加するだけvar formalParamsNode = procDefNode[3]
    formalParamsNode.expectKind( nnkFormalParams )
    var selfIdentNode = newIdentDefs( ident("self"), ident(className) )
    formalParamsNode.insert( 1, selfIdentNode )

macroclassproc(className:untyped,stmtList:untyped):untyped=
    var classNameStr = `$`(className.ident)
    stmtList.traverse do (n:NimNode;parents:refseq[NimNode]) -> TraverseOp:
        case n.kind
        of nnkProcDef, nnkMethodDef, nnkIteratorDef:
            n.convertToMemberProc( classNameStr )
        else: discard
    result = stmtList

procnewClassDef(classNameIdent,baseNameIdent,classBody:NimNode):NimNode=
    ## クラス名ノード、基底クラス名ノード、フィールド定義ノードからクラス定義ノード(実際はtypeセクション)を作成する# [1].とりあえずフィールド無しのobject型定義ノードを作成# [2].classBodyを走査しidentDefsを見つけ次第、[1]内のRecListへコピーしていく#     また、ProcDefを見つけた場合は、第一引数にselfを追加してから、resultノードへProcDefを追加する# [1]
    result = quote:
        type `classNameIdent` = refobjectof `baseNameIdent`

    if classBody == nil: return# RecListノードを取得(なければ作る)var objectTyNode = result.findNode( nnkObjectTy )
    if objectTyNode[2].kind == nnkEmpty:
        objectTyNode.del( 2 )
        objectTyNode.add( newNimNode( nnkRecList ) )
    var recListNode = objectTyNode[2]

    # [2]var result2 = result
    classBody.traverse do (n:NimNode;parents:refseq[NimNode]) -> TraverseOp:
        case n.kind
        # プロシージャの引数にselfを追加of nnkProcDef, nnkMethodDef, nnkIteratorDef:
            var newNode = n.copyNimTree()
            result2.add( newNode )
            newNode.convertToMemberProc( `$`(classNameIdent.ident) )
            return SkipChild
        # 変数定義はフィールド定義へ追加するof nnkIdentDefs:
            recListNode.add( n )
            return SkipChild
        # 型定義は型名をクラス名+型名に変更するof nnkTypeSection:
            # TypeSection内の識別子にクラス名を挿入
            n.traverse do (n:NimNode;parents:refseq[NimNode]) -> TraverseOp:
                case n.kind
                of nnkTypeDef:
                    var parentNode = parents[^1]
                    if parentNode.kind == nnkTypeSection:

                        # クラス名.型名でアクセスできるようにするためのヘルパーtemplateを定義var helperTemplateStr = "template $1(T:typedesc[$2]) : untyped = `T $1`".format( `$`(n[0].ident), `$`(classNameIdent.ident) )

                        n[0].ident = !(`$`(classNameIdent.ident) & `$`(n[0].ident))
                        result2.add( parseStmt( helperTemplateStr ) )
                    return SkipChild
                else:discard
            result2.add( n.copyNimTree() ) # TypeSectionまるごとコピーreturn SkipChild
        else: discardmacroclass(className:untyped,classBody:untyped):untyped=
    # クラス名と基底クラス名を取得var classNameStr:stringvar baseNameStr:stringcase className.len()
    of0: # class a
        classNameStr = `$`(ident(className))
    of2: # class a(b)
        classNameStr = `$`(ident(className[0]))
        baseNameStr = `$`(ident(className[1]))
    of3: # class a of b
        classNameStr = `$`(ident(className[1]))
        baseNameStr = `$`(ident(className[2]))
    else: assertfalse
    result = newClassDef( ident(classNameStr), if baseNameStr!=nil:ident(baseNameStr) else:ident("RootObj"),classBody)

######################## 使用例#######################
class Hoge:
    # クラススコープの型定義 (Hoge.Flagsなどでアクセス可能)type Flags = enum On, Off
    type MyInt = int# メンバ変数定義var x: intvar y: int# メンバ関数定義(第一引数としてself:Hogeが挿入される)proctest(a:int,y:int):bool =
        echo "Hoge.x=", self.x
        returnfalse# クラススコープで定義したenumを利用var f: Hoge.Flags
echo f # On# クラススコープで定義した型を利用var myInt: Hoge.MyInt = 10
echo myInt # 10# あとからメンバ関数を追加するためのマクロもあり
classproc Hoge:
    proctest2(a:int,y:int):bool =
        echo "Hoge.test2()"returnfalse# クラス(object型)として普通に利用できるvar temp = Hoge(x:77)
echo "$1, $2".format( temp.x, temp.y )
discard temp.test(1,2)
discard temp.test2(1,2)

改善案

  • コンストラクタを定義可能に
  • メンバ変数の初期値指定
  • staticメンバ変数
  • staticメンバ関数
  • メンバのアクセス制御(といってもNimはモジュール内外のアクセス制御しかないが…)
  • Self型、Base型エイリアス

Nim by Example の OOPマクロとの比較

このようなクラス定義マクロは公式?の Nim by Exampleにも存在しています。今回作ったマクロも「Nim by Example」のOOPマクロを参考にしています*3

Nim by Example - OOP Macro

「Nim by Example」のOOPマクロと今回作成したclassマクロの違いは以下とおりです。

  • OOPマクロは基底クラスの指定が必須になっている
  • classマクロの方は基底クラスの指定方法としてof以外にclass クラス名(基底クラス名):の形式にも対応している
  • classマクロの方はクラススコープの型定義機能がある

*1:ちなみに名前空間もモジュールごとの名前空間しか存在しません。

*2:実際にはこのようにテキストで展開されるのではなく、ASTとして展開される

*3:コードはマクロの勉強も兼ねて一から書き上げました

10年前のMac mini(2007年製)にLinux(Ubuntu)をインストールする

$
0
0

新年明けて何か新しいことをしようと思い、前々からやりたかったMac miniLinuxマシン化をやってみました。

今回、Linuxマシン化したのは2007年製のMac miniです。つまり10年前のパソコンということです。このMac miniは性能的にはまだまだ使えるはずなのですが、Appleのサポートが打ち切られてしまいOSのバージョンアップは停止、あまり使いたくない感じになってしまっていました。なんとなく怖いですし。そこでLinux化です。年々、無駄にOSを重くしていくMacとは違い、Linuxならこんなマシンでも最新のバージョンをインストールできるだろうという目論見です。Linux自体にも興味があり、ずっと導入してみたかったというのもあります。

MacLinuxをインストールするのはメーカー保証もないので自己責任やりましょう

スペック

今回行ったのは、Mac miniLinuxマシン化です。MacOSとのデュアルブートではなくMacOSLinuxで上書きして完全にLinux専用機としました。まとめるとこんな感じです。

手順

  1. ubuntu公式サイトからisoイメージをダウンロード(今回利用したのはこれ)
  2. DVDに焼く
  3. Mac miniをキーボードの C を押しながら起動する(ディスク起動)
  4. ubuntuインストール画面が出るので流れに従いながら画面を進めていく
  5. 最後に再起動でインストール完了

これだけです。こんなに簡単ならもっと早くやっておけばよかったです。ネット上ではrEFItがどうのこうのと見かけたりしましたが*1、普通にディスク起動でいけました。ただし、nVidiaGPUを積んでいるとubuntuの起動に失敗するとの情報もあるようです。今回使用した2007年製Mac miniにはnVidiaGPUが積まれていなかったので、すんなりとインストールできたのかもしれません。

個人的に引っかかったのは、最初にDVDではなくUSBメモリを起動ディスクとして利用しようとしてしまったところです。もっと新しいMac miniならUSBメモリを起動ディスクとして利用することもできるのかもしれませんが、2007年製Mac miniではUSBメモリを起動ディスクとして認識してくれませんでした。

あと、細かい注意点としてはblutooth接続ではなくUSB接続のマウス、キーボードを使っておいたほうがいいと思います。

Linux(ubuntu)を触ってみた感想

とりあえず触っただけの感じだと、かなりMacに近いというかほぼMacだなと思いました*2。フォントもなかなか綺麗です。ターミナルの色が鮮やかな上に背景が微妙に半透明でいい感じです。最初からFirefoxがインストールされており、Youtubeの動画もそのまま見ることができました。音が最初はMac mini本体から鳴っていたのですが、設定で外部出力に切り替えることができました。このMac miniには光出力端子があるのですが、光出力もちゃんと動作しました。今、このブログ記事も今回インストールしたLinuxFirefoxから書いているのですが、特に問題もなく快適です。と、良いことしか言っていませんが、もっとちゃんと使い始めるといろいろ粗も見えてくるのかなと思います。

*1:USBメモリ経由のディスク起動を行うためにrEFItをインストールしてみましたが、結局USBメモリ経由のディスク起動はできませんでした。

*2:パクリ?


【Nim】コンパイラソースコードリーディングのためのメモ

$
0
0

ファイル

compiler

トークン種類(tk)

lexer.nim

    tkInvalid, tkEof,         # order is important here!
    tkSymbol, # keywords:
    tkAddr, tkAnd, tkAs, tkAsm,
    tkBind, tkBlock, tkBreak, tkCase, tkCast,
    tkConcept, tkConst, tkContinue, tkConverter,
    tkDefer, tkDiscard, tkDistinct, tkDiv, tkDo,
    tkElif, tkElse, tkEnd, tkEnum, tkExcept, tkExport,
    tkFinally, tkFor, tkFrom, tkFunc,
    tkIf, tkImport, tkIn, tkInclude, tkInterface,
    tkIs, tkIsnot, tkIterator,
    tkLet,
    tkMacro, tkMethod, tkMixin, tkMod, tkNil, tkNot, tkNotin,
    tkObject, tkOf, tkOr, tkOut,
    tkProc, tkPtr, tkRaise, tkRef, tkReturn,
    tkShl, tkShr, tkStatic,
    tkTemplate,
    tkTry, tkTuple, tkType, tkUsing,
    tkVar, tkWhen, tkWhile, tkXor,
    tkYield, # end of keywords
    tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit,
    tkUIntLit, tkUInt8Lit, tkUInt16Lit, tkUInt32Lit, tkUInt64Lit,
    tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit,
    tkStrLit, tkRStrLit, tkTripleStrLit,
    tkGStrLit, tkGTripleStrLit, tkCharLit, tkParLe, tkParRi, tkBracketLe,
    tkBracketRi, tkCurlyLe, tkCurlyRi,
    tkBracketDotLe, tkBracketDotRi, # [. and  .]
    tkCurlyDotLe, tkCurlyDotRi, # {.  and  .}
    tkParDotLe, tkParDotRi,   # (. and .)
    tkComma, tkSemiColon,
    tkColon, tkColonColon, tkEquals, tkDot, tkDotDot,
    tkOpr, tkComment, tkAccent,
    tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr

ASTノード種類(nk)

ast.nim

type
  TNodeKind* = enum# order is extremely important, because ranges are used# to check whether a node belongs to a certain class
    nkNone,               # unknown node kind: indicates an error# Expressions:# Atoms:
    nkEmpty,              # the node is empty
    nkIdent,              # node is an identifier
    nkSym,                # node is a symbol
    nkType,               # node is used for its typ field

    nkCharLit,            # a character literal ''
    nkIntLit,             # an integer literal
    nkInt8Lit,
    nkInt16Lit,
    nkInt32Lit,
    nkInt64Lit,
    nkUIntLit,            # an unsigned integer literal
    nkUInt8Lit,
    nkUInt16Lit,
    nkUInt32Lit,
    nkUInt64Lit,
    nkFloatLit,           # a floating point literal
    nkFloat32Lit,
    nkFloat64Lit,
    nkFloat128Lit,
    nkStrLit,             # a string literal ""
    nkRStrLit,            # a raw string literal r""
    nkTripleStrLit,       # a triple string literal """
    nkNilLit,             # the nil literal# end of atoms
    nkMetaNode_Obsolete,  # difficult to explain; represents itself# (used for macros)
    nkDotCall,            # used to temporarily flag a nkCall node;# this is used# for transforming ``s.len`` to ``len(s)``

    nkCommand,            # a call like ``p 2, 4`` without parenthesis
    nkCall,               # a call like p(x, y) or an operation like +(a, b)
    nkCallStrLit,         # a call with a string literal# x"abc" has two sons: nkIdent, nkRStrLit# x"""abc""" has two sons: nkIdent, nkTripleStrLit
    nkInfix,              # a call like (a + b)
    nkPrefix,             # a call like !a
    nkPostfix,            # something like a! (also used for visibility)
    nkHiddenCallConv,     # an implicit type conversion via a type converter

    nkExprEqExpr,         # a named parameter with equals: ''expr = expr''
    nkExprColonExpr,      # a named parameter with colon: ''expr: expr''
    nkIdentDefs,          # a definition like `a, b: typeDesc = expr`# either typeDesc or expr may be nil; used in# formal parameters, var statements, etc.
    nkVarTuple,           # a ``var (a, b) = expr`` construct
    nkPar,                # syntactic (); may be a tuple constructor
    nkObjConstr,          # object constructor: T(a: 1, b: 2)
    nkCurly,              # syntactic {}
    nkCurlyExpr,          # an expression like a{i}
    nkBracket,            # syntactic []
    nkBracketExpr,        # an expression like a[i..j, k]
    nkPragmaExpr,         # an expression like a{.pragmas.}
    nkRange,              # an expression like i..j
    nkDotExpr,            # a.b
    nkCheckedFieldExpr,   # a.b, but b is a field that needs to be checked
    nkDerefExpr,          # a^
    nkIfExpr,             # if as an expression
    nkElifExpr,
    nkElseExpr,
    nkLambda,             # lambda expression
    nkDo,                 # lambda block appering as trailing proc param
    nkAccQuoted,          # `a` as a node

    nkTableConstr,        # a table constructor {expr: expr}
    nkBind,               # ``bind expr`` node
    nkClosedSymChoice,    # symbol choice node; a list of nkSyms (closed)
    nkOpenSymChoice,      # symbol choice node; a list of nkSyms (open)
    nkHiddenStdConv,      # an implicit standard type conversion
    nkHiddenSubConv,      # an implicit type conversion from a subtype# to a supertype
    nkConv,               # a type conversion
    nkCast,               # a type cast
    nkStaticExpr,         # a static expr
    nkAddr,               # a addr expression
    nkHiddenAddr,         # implicit address operator
    nkHiddenDeref,        # implicit ^ operator
    nkObjDownConv,        # down conversion between object types
    nkObjUpConv,          # up conversion between object types
    nkChckRangeF,         # range check for floats
    nkChckRange64,        # range check for 64 bit ints
    nkChckRange,          # range check for ints
    nkStringToCString,    # string to cstring
    nkCStringToString,    # cstring to string# end of expressions

    nkAsgn,               # a = b
    nkFastAsgn,           # internal node for a fast ``a = b``# (no string copy)
    nkGenericParams,      # generic parameters
    nkFormalParams,       # formal parameters
    nkOfInherit,          # inherited from symbol

    nkImportAs,           # a 'as' b in an import statement
    nkProcDef,            # a proc
    nkMethodDef,          # a method
    nkConverterDef,       # a converter
    nkMacroDef,           # a macro
    nkTemplateDef,        # a template
    nkIteratorDef,        # an iterator

    nkOfBranch,           # used inside case statements# for (cond, action)-pairs
    nkElifBranch,         # used in if statements
    nkExceptBranch,       # an except section
    nkElse,               # an else part
    nkAsmStmt,            # an assembler block
    nkPragma,             # a pragma statement
    nkPragmaBlock,        # a pragma with a block
    nkIfStmt,             # an if statement
    nkWhenStmt,           # a when expression or statement
    nkForStmt,            # a for statement
    nkParForStmt,         # a parallel for statement
    nkWhileStmt,          # a while statement
    nkCaseStmt,           # a case statement
    nkTypeSection,        # a type section (consists of type definitions)
    nkVarSection,         # a var section
    nkLetSection,         # a let section
    nkConstSection,       # a const section
    nkConstDef,           # a const definition
    nkTypeDef,            # a type definition
    nkYieldStmt,          # the yield statement as a tree
    nkDefer,              # the 'defer' statement
    nkTryStmt,            # a try statement
    nkFinally,            # a finally section
    nkRaiseStmt,          # a raise statement
    nkReturnStmt,         # a return statement
    nkBreakStmt,          # a break statement
    nkContinueStmt,       # a continue statement
    nkBlockStmt,          # a block statement
    nkStaticStmt,         # a static statement
    nkDiscardStmt,        # a discard statement
    nkStmtList,           # a list of statements
    nkImportStmt,         # an import statement
    nkImportExceptStmt,   # an import x except a statement
    nkExportStmt,         # an export statement
    nkExportExceptStmt,   # an 'export except' statement
    nkFromStmt,           # a from * import statement
    nkIncludeStmt,        # an include statement
    nkBindStmt,           # a bind statement
    nkMixinStmt,          # a mixin statement
    nkUsingStmt,          # an using statement
    nkCommentStmt,        # a comment statement
    nkStmtListExpr,       # a statement list followed by an expr; this is used# to allow powerful multi-line templates
    nkBlockExpr,          # a statement block ending in an expr; this is used# to allowe powerful multi-line templates that open a# temporary scope
    nkStmtListType,       # a statement list ending in a type; for macros
    nkBlockType,          # a statement block ending in a type; for macros# types as syntactic trees:

    nkWith,               # distinct with `foo`
    nkWithout,            # distinct without `foo`

    nkTypeOfExpr,         # type(1+2)
    nkObjectTy,           # object body
    nkTupleTy,            # tuple body
    nkTupleClassTy,       # tuple type class
    nkTypeClassTy,        # user-defined type class
    nkStaticTy,           # ``static[T]``
    nkRecList,            # list of object parts
    nkRecCase,            # case section of object
    nkRecWhen,            # when section of object
    nkRefTy,              # ``ref T``
    nkPtrTy,              # ``ptr T``
    nkVarTy,              # ``var T``
    nkConstTy,            # ``const T``
    nkMutableTy,          # ``mutable T``
    nkDistinctTy,         # distinct type
    nkProcTy,             # proc type
    nkIteratorTy,         # iterator type
    nkSharedTy,           # 'shared T'# we use 'nkPostFix' for the 'not nil' addition
    nkEnumTy,             # enum body
    nkEnumFieldDef,       # `ident = expr` in an enumeration
    nkArgList,            # argument list
    nkPattern,            # a special pattern; used for matching
    nkReturnToken,        # token used for interpretation
    nkClosure,            # (prc, env)-pair (internally used for code gen)
    nkGotoState,          # used for the state machine (for iterators)
    nkState,              # give a label to a code section (for iterators)
    nkBreakState,         # special break statement for easier code generation
    nkFuncDef             # a func

シンボル種類(sk)

ast.nim

  TSymKind* = enum# the different symbols (start with the prefix sk);# order is important for the documentation generator!
    skUnknown,            # unknown symbol: used for parsing assembler blocks# and first phase symbol lookup in generics
    skConditional,        # symbol for the preprocessor (may become obsolete)
    skDynLib,             # symbol represents a dynamic library; this is used# internally; it does not exist in Nim code
    skParam,              # a parameter
    skGenericParam,       # a generic parameter; eq in ``proc x[eq=`==`]()``
    skTemp,               # a temporary variable (introduced by compiler)
    skModule,             # module identifier
    skType,               # a type
    skVar,                # a variable
    skLet,                # a 'let' symbol
    skConst,              # a constant
    skResult,             # special 'result' variable
    skProc,               # a proc
    skFunc,               # a func
    skMethod,             # a method
    skIterator,           # an iterator
    skConverter,          # a type converter
    skMacro,              # a macro
    skTemplate,           # a template; currently also misused for user-defined# pragmas
    skField,              # a field in a record or object
    skEnumField,          # an identifier in an enum
    skForVar,             # a for loop variable
    skLabel,              # a label (for block statement)
    skStub,               # symbol is a stub and not yet loaded from the ROD# file (it is loaded on demand, which may# mean: never)
    skPackage,            # symbol is a package (used for canonicalization)
    skAlias               # an alias (needs to be resolved immediately)

シンボルフラグ(sf)

ast.nim

type
  TSymFlag* = enum# already 33 flags!
    sfUsed,           # read access of sym (for warnings) or simply used
    sfExported,       # symbol is exported from module
    sfFromGeneric,    # symbol is instantiation of a generic; this is needed# for symbol file generation; such symbols should always# be written into the ROD file
    sfGlobal,         # symbol is at global scope

    sfForward,        # symbol is forward declared
    sfImportc,        # symbol is external; imported
    sfExportc,        # symbol is exported (under a specified name)
    sfVolatile,       # variable is volatile
    sfRegister,       # variable should be placed in a register
    sfPure,           # object is "pure" that means it has no type-information# enum is "pure", its values need qualified access# variable is "pure"; it's an explicit "global"
    sfNoSideEffect,   # proc has no side effects
    sfSideEffect,     # proc may have side effects; cannot prove it has none
    sfMainModule,     # module is the main module
    sfSystemModule,   # module is the system module
    sfNoReturn,       # proc never returns (an exit proc)
    sfAddrTaken,      # the variable's address is taken (ex- or implicitly);# *OR*: a proc is indirectly called (used as first class)
    sfCompilerProc,   # proc is a compiler proc, that is a C proc that is# needed for the code generator
    sfProcvar,        # proc can be passed to a proc var
    sfDiscriminant,   # field is a discriminant in a record/object
    sfDeprecated,     # symbol is deprecated
    sfExplain,        # provide more diagnostics when this symbol is used
    sfError,          # usage of symbol should trigger a compile-time error
    sfShadowed,       # a symbol that was shadowed in some inner scope
    sfThread,         # proc will run as a thread# variable is a thread variable
    sfCompileTime,    # proc can be evaluated at compile time
    sfConstructor,    # proc is a C++ constructor
    sfDeadCodeElim,   # dead code elimination for the module is turned on
    sfBorrow,         # proc is borrowed
    sfInfixCall,      # symbol needs infix call syntax in target language;# for interfacing with C++, JS
    sfNamedParamCall, # symbol needs named parameter call syntax in target# language; for interfacing with Objective C
    sfDiscardable,    # returned value may be discarded implicitly
    sfOverriden,      # proc is overriden
    sfGenSym          # symbol is 'gensym'ed; do not add to symbol tableconst
  sfDispatcher* = sfDeadCodeElim # copied method symbol is the dispatcher
  sfNoInit* = sfMainModule       # don't generate code to init the variable

  sfImmediate* = sfDeadCodeElim
    # macro or template is immediately expanded# without considering any possible overloads
  sfAllUntyped* = sfVolatile # macro or template is immediately expanded \# in a generic context

  sfDirty* = sfPure
    # template is not hygienic (old styled template)# module, compiled from a dirty-buffer

  sfAnon* = sfDiscardable
    # symbol name that was generated by the compiler# the compiler will avoid printing such names# in user messages.

  sfNoForward* = sfRegister
    # forward declarations are not required (per module)
  sfReorder* = sfForward
    # reordering pass is enabled

  sfCompileToCpp* = sfInfixCall       # compile the module as C++ code
  sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code
  sfExperimental* = sfOverriden       # module uses the .experimental switch
  sfGoto* = sfOverriden               # var is used for 'goto' code generation
  sfWrittenTo* = sfBorrow             # param is assigned to
  sfEscapes* = sfProcvar              # param escapes
  sfBase* = sfDiscriminant
  sfIsSelf* = sfOverriden             # param is 'self'

内部型種類(ty)

コンパイラ内部で扱う型の種類一覧

ast.nim

type
  TTypeKind* = enum# order is important!# Don't forget to change hti.nim if you make a change here# XXX put this into an include file to avoid this issue!# several types are no longer used (guess which), but a# spot in the sequence is kept for backwards compatibility# (apparently something with bootstrapping)# if you need to add a type, they can apparently be reused
    tyNone, tyBool, tyChar,
    tyEmpty, tyAlias, tyNil, tyExpr, tyStmt, tyTypeDesc,
    tyGenericInvocation, # ``T[a, b]`` for types to invoke
    tyGenericBody,       # ``T[a, b, body]`` last parameter is the body
    tyGenericInst,       # ``T[a, b, realInstance]`` instantiated generic type# realInstance will be a concrete type like tyObject# unless this is an instance of a generic alias type.# then realInstance will be the tyGenericInst of the# completely (recursively) resolved alias.

    tyGenericParam,      # ``a`` in the above patterns
    tyDistinct,
    tyEnum,
    tyOrdinal,           # integer types (including enums and boolean)
    tyArray,
    tyObject,
    tyTuple,
    tySet,
    tyRange,
    tyPtr, tyRef,
    tyVar,
    tySequence,
    tyProc,
    tyPointer, tyOpenArray,
    tyString, tyCString, tyForward,
    tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers
    tyFloat, tyFloat32, tyFloat64, tyFloat128,
    tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64,
    tyOptAsRef, tyUnused1, tyUnused2,
    tyVarargs,
    tyUnused,
    tyProxy # used as errornous type (for idetools)

    tyBuiltInTypeClass
      # Type such as the catch-all object, tuple, seq, etc

    tyUserTypeClass
      # the body of a user-defined type class

    tyUserTypeClassInst
      # Instance of a parametric user-defined type class.# Structured similarly to tyGenericInst.# tyGenericInst represents concrete types, while# this is still a "generic param" that will bind types# and resolves them during sigmatch and instantiation.

    tyCompositeTypeClass
      # Type such as seq[Number]# The notes for tyUserTypeClassInst apply here as well# sons[0]: the original expression used by the user.# sons[1]: fully expanded and instantiated meta type# (potentially following aliases)

    tyInferred
      # In the initial state `base` stores a type class constraining# the types that can be inferred. After a candidate type is# selected, it's stored in `lastSon`. Between `base` and `lastSon`# there may be 0, 2 or more types that were also considered as# possible candidates in the inference process (i.e. lastSon will# be updated to store a type best conforming to all candidates)

    tyAnd, tyOr, tyNot
      # boolean type classes such as `string|int`,`not seq`,# `Sortable and Enumable`, etc

    tyAnything
      # a type class matching any type

    tyStatic
      # a value known at compile type (the underlying type is .base)

    tyFromExpr
      # This is a type representing an expression that depends# on generic parameters (the expression is stored in t.n)# It will be converted to a real type only during generic# instantiation and prior to this it has the potential to# be any type.

    tyOpt
      # Builtin optional type

    tyVoid
      # now different from tyEmpty, hurray!static:
  # remind us when TTypeKind stops to fit in a single 64-bit wordassert TTypeKind.high.ord<= 63const
  tyPureObject* = tyTuple
  GcTypeKinds* = {tyRef, tySequence, tyString}
  tyError* = tyProxy # as an errornous node should match everything
  tyUnknown* = tyFromExpr

  tyUnknownTypes* = {tyError, tyFromExpr}

  tyTypeClasses* = {tyBuiltInTypeClass, tyCompositeTypeClass,
                    tyUserTypeClass, tyUserTypeClassInst,
                    tyAnd, tyOr, tyNot, tyAnything}

  tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses
  tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst}

内部型フラグ(tf)

  TTypeFlag* = enum# keep below 32 for efficiency reasons (now: beyond that)
    tfVarargs,        # procedure has C styled varargs# tyArray type represeting a varargs list
    tfNoSideEffect,   # procedure type does not allow side effects
    tfFinal,          # is the object final?
    tfInheritable,    # is the object inheritable?
    tfAcyclic,        # type is acyclic (for GC optimization)
    tfEnumHasHoles,   # enum cannot be mapped into a range
    tfShallow,        # type can be shallow copied on assignment
    tfThread,         # proc type is marked as ``thread``; alias for ``gcsafe``
    tfFromGeneric,    # type is an instantiation of a generic; this is needed# because for instantiations of objects, structural# type equality has to be used
    tfUnresolved,     # marks unresolved typedesc/static params: e.g.# proc foo(T: typedesc, list: seq[T]): var T# proc foo(L: static[int]): array[L, int]# can be attached to ranges to indicate that the range# can be attached to generic procs with free standing# type parameters: e.g. proc foo[T]()# depends on unresolved static params.
    tfResolved        # marks a user type class, after it has been bound to a# concrete type (lastSon becomes the concrete type)
    tfRetType,        # marks return types in proc (used to detect type classes# used as return types for return type inference)
    tfCapturesEnv,    # whether proc really captures some environment
    tfByCopy,         # pass object/tuple by copy (C backend)
    tfByRef,          # pass object/tuple by reference (C backend)
    tfIterator,       # type is really an iterator, not a tyProc
    tfPartial,        # type is declared as 'partial'
    tfNotNil,         # type cannot be 'nil'

    tfNeedsInit,      # type constains a "not nil" constraint somewhere or some# other type so that it requires initialization
    tfVarIsPtr,       # 'var' type is translated like 'ptr' even in C++ mode
    tfHasMeta,        # type contains "wildcard" sub-types such as generic params# or other type classes
    tfHasGCedMem,     # type contains GC'ed memory
    tfPacked
    tfHasStatic
    tfGenericTypeParam
    tfImplicitTypeParam
    tfInferrableStatic
    tfExplicit        # for typedescs, marks types explicitly prefixed with the# `type` operator (e.g. type int)
    tfWildcard        # consider a proc like foo[T, I](x: Type[T, I])# T and I here can bind to both typedesc and static types# before this is determined, we'll consider them to be a# wildcard type.
    tfHasAsgn         # type has overloaded assignment operator
    tfBorrowDot       # distinct type borrows '.'
    tfTriggersCompileTime # uses the NimNode type which make the proc# implicitly '.compiletime'
    tfRefsAnonObj     # used for 'ref object' and 'ptr object'
    tfCovariant       # covariant generic param mimicing a ptr type
    tfWeakCovariant   # covariant generic param mimicing a seq/array type
    tfContravariant   # contravariant generic paramconst
  routineKinds* = {skProc, skFunc, skMethod, skIterator,
                   skConverter, skMacro, skTemplate}
  tfIncompleteStruct* = tfVarargs
  tfUncheckedArray* = tfVarargs
  tfUnion* = tfNoSideEffect
  tfGcSafe* = tfThread
  tfObjHasKids* = tfEnumHasHoles
  tfOldSchoolExprStmt* = tfVarargs # for now used to distinguish \# 'varargs[expr]' from 'varargs[untyped]'. Eventually 'expr' will be# deprecated and this mess can be cleaned up.
  tfReturnsNew* = tfInheritable
  skError* = skUnknown

  # type flags that are essential for type equality:
  eqTypeFlags* = {tfIterator, tfNotNil, tfVarIsPtr}

コンパイラマジック定数(m)

Nim/ast.nim at 844e123061631d4cc5bc4300301d1865eda5a337 · nim-lang/Nim

type
  TMagic* = enum# symbols that require compiler magic:
    mNone,
    mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn,
    mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mTypeOf, mRoof, mPlugin,
    mEcho, mShallowCopy, mSlurp, mStaticExec,
    mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst,
    mUnaryLt, mInc, mDec, mOrd,
    mNew, mNewFinalize, mNewSeq, mNewSeqOfCap,
    mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq,
    mXLenStr, mXLenSeq,
    mIncl, mExcl, mCard, mChr,
    mGCref, mGCunref,
    mAddI, mSubI, mMulI, mDivI, mModI,
    mSucc, mPred,
    mAddF64, mSubF64, mMulF64, mDivF64,
    mShrI, mShlI, mBitandI, mBitorI, mBitxorI,
    mMinI, mMaxI,
    mMinF64, mMaxF64,
    mAddU, mSubU, mMulU, mDivU, mModU,
    mEqI, mLeI, mLtI,
    mEqF64, mLeF64, mLtF64,
    mLeU, mLtU,
    mLeU64, mLtU64,
    mEqEnum, mLeEnum, mLtEnum,
    mEqCh, mLeCh, mLtCh,
    mEqB, mLeB, mLtB,
    mEqRef, mEqUntracedRef, mLePtr, mLtPtr,
    mXor, mEqCString, mEqProc,
    mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot,
    mUnaryPlusI, mBitnotI,
    mUnaryPlusF64, mUnaryMinusF64, mAbsF64,
    mZe8ToI, mZe8ToI64,
    mZe16ToI, mZe16ToI64,
    mZe32ToI64, mZeIToI64,
    mToU8, mToU16, mToU32,
    mToFloat, mToBiggestFloat,
    mToInt, mToBiggestInt,
    mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr,
    mStrToStr, mEnumToStr,
    mAnd, mOr,
    mEqStr, mLeStr, mLtStr,
    mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mSymDiffSet,
    mConStrStr, mSlice,
    mDotDot, # this one is only necessary to give nice compile time warnings
    mFields, mFieldPairs, mOmpParFor,
    mAppendStrCh, mAppendStrStr, mAppendSeqElem,
    mInRange, mInSet, mRepr, mExit,
    mSetLengthStr, mSetLengthSeq,
    mIsPartOf, mAstToStr, mParallel,
    mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
    mNewString, mNewStringOfCap, mParseBiggestFloat,
    mReset,
    mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
    mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
    mOrdinal,
    mInt, mInt8, mInt16, mInt32, mInt64,
    mUInt, mUInt8, mUInt16, mUInt32, mUInt64,
    mFloat, mFloat32, mFloat64, mFloat128,
    mBool, mChar, mString, mCstring,
    mPointer, mEmptySet, mIntSetBaseType, mNil, mExpr, mStmt, mTypeDesc,
    mVoidType, mPNimrodNode, mShared, mGuarded, mLock, mSpawn, mDeepCopy,
    mIsMainModule, mCompileDate, mCompileTime, mProcCall,
    mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType,
    mNaN, mInf, mNegInf,
    mCompileOption, mCompileOptionArg,
    mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
    mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
    mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
    mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
    mNBindSym, mLocals, mNCallSite,
    mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl,
    mNHint, mNWarning, mNError,
    mInstantiationInfo, mGetTypeInfo, mNGenSym,
    mNimvm, mIntDefine, mStrDefine

【Nim】マクロプログラミング用メモ

$
0
0

ドキュメント :
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

その他

ノード情報操作/取得

ASTノードを文字列化する

  • マクロ内で使う(NimNodeを渡す)
  • マクロ外で使う(コードブロックを渡す)
    • 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 {..}

Macでビルドした実行ファイルが、libJPEGが原因で落ちる場合の対処法

$
0
0

久しぶりにMacでビルドしたプロジェクトが以下のようなエラー出力とともに実行時に落ちるようになっていました。

dyld: Symbol not found: __cg_jpeg_resync_to_restart
  Referenced from: /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO
  Expected in: /usr/local/lib/libJPEG.dylib
 in /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO

この原因は、Macの何かのフレームワークが本来のlibJPEGとは別のlibJPEGを使用(リンク)してしまっているからのようです。そして、その別のlibJPEGとはHomebrewでインストールしたものらしい。

対処法として、HomebrewでそのlibJEPGをアンインストールすればよいというのを見かけましたが、手元でアンインストールを試みたところ、他のパッケージから使われているっぽいので止めておきました。

他の対処法として、シンボリックリンクを付け替えるというのがあり、今回はそちらで対処しました。ターミナルで以下のように操作します。一応操作前のlibjpegを.backupで残すようになっています。※このコードはこちらに載っていたものです。

cd /usr/local/lib
mv libjpeg.dylib libjpeg.dylib.backup
ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libJPEG.dylib libJPEG.dylib
mv libtiff.dylib libtiff.dylib.backup
ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libTIFF.dylib libTIFF.dylib
mv libpng.dylib libpng.dylib.backup
ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libPng.dylib libPNG.dylib

これでとりあえず実行できるようになりました。ただし、この方法はシンボリックリンクを付け替える前のlibJPEGを利用するプログラムで何か問題がでるかもしれません。注意してください。

参考

【OpenGL】個人的逆引きリファレンス

$
0
0
  • 個人的な覚書としての逆引きリファレンスです
  • 今のところ OpenGL ES 2.0 のみを対象としています
  • 思いついたら追加していきます

現在のフレームバッファ(カラーバッファ)のサイズを取得する

bool GetCurrentColorBufferSize( GLint& width, GLint& height )
{
    // 現在バインドされているフレームバッファ名を取得
    GLint currentFrameBufferName = 0;
    glGetIntegerv( GL_FRAMEBUFFER_BINDING, &currentFrameBufferName );

    // フレームバッファがバインドされていないと情報が取得できないif ( currentFrameBufferName == 0 ) { returnfalse; }

    // あとで元に戻すために現在バインドされているレンダーバッファ名を取得
    GLint oldRenderBufferName = 0;
    glGetIntegerv( GL_RENDERBUFFER_BINDING, &oldRenderBufferName );

    // 現在のフレームバッファにアタッチされているカラーバッファのレンダーバッファ名を取得
    GLint colorBufferName = 0;
    glGetFramebufferAttachmentParameteriv( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorBufferName );

    // カラーバッファがアタッチされていない?if ( colorBufferName == 0 ) { returnfalse; }

    // レンダーバッファ(カラーバッファ)をバインド
    glBindRenderbuffer( GL_RENDERBUFFER, colorBufferName );

    // カラーバッファの幅と高さを取得
    glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width );
    glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height );

    // レンダーバッファのバインドを元に戻す
    glBindRenderbuffer( GL_RENDERBUFFER, oldRenderBufferName );

    returntrue;
}

iOSでデフォルトのフレームバッファをバインドする(GLKViewControllerを使用している場合)

GLKViewのbindDrawableメソッドを使う。GLKViewControllerのviewDidLoad呼び出し時などはフレームバッファがバインドされていないので、フレームバッファ関連のOpenGL関数を使うにはこの方法で事前にバインドしておく必要がある。

GLKView *view = (GLKView *)self.view;
[view bindDrawable];

リファレンス : bindDrawable - GLKView | Apple Developer Documentation
参考 : xcode - iOS GLKit and back to default framebuffer - Stack Overflow

BlocksとGCD(Grand Central Dispatch)についてのメモ

$
0
0

久しぶりにiOSプログラミングをしてみたところ、GCDについてさっぱり忘れていたので、次からはすぐに思い出せるようにメモ。

Blocksとは

Blocksは他の言語でいうところの無名関数、またはクロージャ。BlocksはAppleによるC言語の独自拡張機能なのでXcode付属のコンパイラでしか使えない。ObjectiveCはC言語を内包しているから、ObjectiveCでもBlocksを使うことができる。

GCDではBlocksを処理の単位として扱う。

Blocksメモ

  • キャプチャされた変数はキャプチャ時点のコピーとなる(参照ではない)
  • キャプチャされた変数をBlock内で書き換えるには、その変数の宣言時に__block指定子が付加されている必要がる

GCD(Grand Central Dispatch)とは

非同期処理を楽に行うためにAppleが作った仕組み。Dispatch Queueと呼ばれるキューに処理(Blocks)を追加するだけなので本当に楽。

Dispatch Queue

処理を実行するためのキュー。2種類ある。

Dispatch Queueの種類

  • Serial Dispatch Queue : 追加されたタスクを順番に実行する。1個のDispatch Queueにつき、1スレッド利用するので作り過ぎに注意。
  • Concurrent Dispatch Queue : 追加されたタスクを並行に実行する

Dispatch Queueの入手

Dispatch Queueは自分で作成することもできるし、システムが用意しているものを利用することもできる。システムが用意しているDispatch Queueは以下のものがある。

  • Main Dispatch Queue : メインスレッドで実行されるSerial Dispatch Queue
  • Global Dispatch Queue : 優先度別に4つあるConcurrent Dispatch Queue

入手方法は逆引きを参照

逆引き

リンク

Pillow(Python画像処理ライブラリ)でRGBAパレット形式のTGAファイルを保存する

$
0
0

PillowPythonの画像処理ライブラリです。PillowはTGAファイルに対応しているのですが、RGBAパレット形式のTGAファイルの保存ができなかったのでソースコードをいじって対応させてみました。

ちなみにRGB形式のパレットには最初から対応されていました。

注意事項

  • 確認したPillowのバージョンは'2.8.2'です。ただし、最新のリポジトリでもRGBAのパレットには対応してないようです。
  • 保存しか試していません。(読み込みは試していません)
  • ソースコードいじるのは自己責任でお願いします。
  • RGBAパレット形式のTGAファイルを正しく表示できるビューワーはあまりないようです。いくつかの画像ソフトで試してみましたが全部正しく表示されませんでした。TGAのフォーマット的にはRGBAのパレットは許可されていたと思うのですが…。

いじるファイルは以下のファイルだけです。ファイルの場所は、OSや環境によって変わると思います。私の環境(Mac)では以下の場所にありました。

/usr/local/lib/python2.7/site-packages/PIL/TgaImagePlugin.py

変更箇所

変更は_saveメソッド内の2箇所だけです。

160行目あたり :

        colormapfirst, colormaplength, colormapentry = 0, 256, 24

の次の行に以下のコードを追加

if im.im.getpalettemode() == 'RGBA':
            colormapentry = 32

186(188)行目あたり :

if colormaptype:
        fp.write(im.im.getpalette("RGB", "BGR"))

を以下のコードに差し替え

if colormaptype:
        if colormapentry == 32:
            fp.write(im.im.getpalette("RGBA", "BGRA"))
        else:
            fp.write(im.im.getpalette("RGB", "BGR"))

変更後の_saveメソッドです。これをコピーして差し替えるのが楽かもしれません。

おまけ : Pillowを使ったPNGからTGA(パレット形式)への変換

from PIL import Image
Image.open( 'hoge.png' ).convert( 'P' ).save( 'hoge.tga' )

Macでフォルダアイコンの色を変更する方法とGitの罠

$
0
0

Macで作業をしていて、ふとフォルダアイコンの色を変えたくなりました。調べてみたところ、Mac標準の機能だけでできることがわかったので紹介したいと思います。また、この方法はGitでちょっと問題がでるのでその対処法も紹介します。

フォルダアイコン色の変更手順

この動画を見たほうが早いかもしれません。紹介する方法もこの動画とほぼ同じものです。

How to Change Folder Colors in OS X - YouTube

  • 1 . Finder上で色を変えたいフォルダの「情報を見る」を選択 ->ファイル情報の小窓を表示されます
  • 2 . 小窓の左上のアイコンをクリックして選択し、コピー(Command+C)
  • 3 . Mac標準アプリの「プレビュー」を起動し、メニューの「ファイル」>「クリップボードから新規作成」を選択 ->フォルダアイコン画像がいくつか表示されます
  • 4 . 並んだフォルダアイコンの一番上が選択されていることを確認し、メニューの「ツール」>「カラーを調整」を選択 ->カラー調整のウインドウが表示されます
  • 5 . カラー調整ウインドウで適当に色を変えます。RGB値で変えられるわけではないのでちょっとコツがいります
  • 6 . カラー調整が終わったらカラー調整ウインドウを閉じて、コピー(Command+C)
  • 7 . Finderのファイル情報の小窓に戻り、小窓の左上のアイコンをクリックして選択肢、ペースト(Command+V) ->フォルダアイコンが変更されます

他のフォルダも同じアイコンにしたい場合は、ファイル情報小窓の左上のアイコンをコピー&ペーストすれば同じアイコンにすることができます。

フォルダアイコンを元に戻す方法

フォルダアイコンを変更すると、その変更情報が「Icon」という名前の隠しファイルとして保存されます。なのでこの「Icon」ファイルを削除すれば元に戻ります。隠しファイルをFinder上に表示させる方法はググッてください(笑)。個人的にはXtraFinderの拡張機能を使うのがおすすめです。

見た目を元に戻すだけなら、通常のフォルダのファイル情報小窓の左上のアイコンをコピー&ペーストすれば見た目は元に戻りますが、「Icon」ファイルは存在したままになります。「Icon」ファイルの存在が気にならなければこの方法でも良いでしょう。

Gitの罠

ところで、この「Icon」ファイル、Gitでうまく扱うことができません。Gitで扱おうとするとエラーが発生してしまいます。しょうがないので「Icon」ファイルをGitの無視ファイルリストに入れようとしても通常の方法ではうまくいきません。なぜならこの「Icon」ファイルの実際のファイル名は「Icon\r\r」だからです。

「Icon」を無視リストへ追加する方法はいくつかあるようでしたが、私がやってうまくいったのはPythonで直接「Icon\r\r」を書き込む方法でした。

f = open('ファイルの場所/.gitignore_global','a+')
f.write( 'Icon\r\r' )
f.close()

ワンライナー的に書くとこうなります。

python -c "f = open('ファイルの場所/.gitignore_global','a+');f.write( 'Icon\r\r' );f.close()"

他の方法はこの辺に。

osx - How to ignore Icon? in git - Stack Overflow

ちなみにVimを使って「Icon\r\r」を書き込む方法は私の環境ではうまくいきませんでした。

参考


【Lua組み込み】Luaスクリプトのファイル分割を独自のファイルロード処理で行う方法

$
0
0

require関数やdofile関数のファイルロード処理はカスタマイズできない

Luaスクリプトを書いていてファイル分割をしたくなった場合、Lua標準のrequire関数やdofile関数を使うと思います。

dofile( 'common.lua' )
dofile( 'util.lua' )

-- いろんな処理

問題なのはファイルロード処理を独自の方法で行いたい場合です。require関数やdofile関数はファイルロードにC標準関数のfopenを使うようになっており、ファイルロードをカスタマイズする仕組みも特に用意されていません。なので、ファイルのロード処理をfopenではなく別の方法で行わせたい場合はLuaソースコードを書き換えるしかありません。面倒くさいですね。

自分でファイルロード処理を行う関数を登録する

よく考えてみると、require関数やdofile関数にこだわる必要はありません。ファイルをロードしLuaスクリプトを実行するC関数を自分で作ってLuaに登録すればいいのです。

例えば、自作する関数の名前をincludeとした場合Luaスクリプト側はこうなります。

include( 'common.lua' )
include( 'util.lua' )

-- いろんな処理

このinclude関数の実装は以下のようになるでしょう。

// グルー関数の定義int lua_include_glue( lua_State* p_luaState )
{
    constchar* p_fileName = lua_tostring( p_luaState, -1 ); // include関数の引数を取得constchar* p_luaCode = MyFileLoad( p_fileName ); // 独自のファイルロード処理
    luaL_dostring( p_luaState, p_luaCode ); // 文字列をLuaスクリプトとして実行
    lua_settop( p_luaState, 0 ); // スタックをクリアreturn0; // Luaへの戻り値は無し
}
// グルー関数のLuaへの登録
lua_register( p_luaState, "include", lua_include_glue );

ポイントはluaL_dostringを使うことです。この関数を使うことでLuaにファイルロードさせるのではなく、自分でロードしたスクリプト文字列を実行させることができます。

まとめ

独自のファイルロード処理でLuaスクリプトをファイル分割したい場合は、自分で関数を作ってしまいましょう。

【メモ】気になるツールなど

$
0
0

そのうち試してみたいツールなどを忘れないようにメモ。

プログラム系

  • プログラミング言語
    • JavaScript : Webサイトだけでなくいろいろな場面で使われてるので覚えておいて損はなさそう。
    • Nim : インデント構文、静的型、強力なマクロ、そしてC言語コードを生成。まだ若い?
    • Haxe : モダンな文法。C言語コード生成可能。
    • Swift : すごく良さげ。C言語Python並にいろんなプラットフォームで動くようになるといいんだけど。
    • Scala : Swift並に良さげな言語。JavaVMから独立したら使いたい。
  • ライブラリ
  • 開発ツール
    • CMake : ビルドツール。よく使われている気がする。QtCreatorもCMakeをサポートし始めたらしい。
  • その他

グラフィック系

サウンド系

  • GarageBand : ピコピコサウンドも作れるらしい

ウェブ系

  • Greasemonkey : スクリプトによってウェブページの表示を変えることができるFirefoxアドオン。自分でスクリプトを書けるようになれば、効率的に情報収集ができるようになりそう。
  • Bootstrap : いい感じの見た目のWebサイトを手早く作るためのライブラリ
  • Sass : CSS拡張言語。CSS手書きはもういやだ。

その他

【C++ アイデア】関数内でメンバ変数を定義する

$
0
0

メンバ関数の中でメンバ変数の定義ができたら便利だろうなと思ったので、方法を模索してみました。半分ネタです。

やりたいのはこういう感じのことです。

class A
{
    int m_Value; // こっちは普通のメンバ変数public:
    void Func( void )
    {
        local int m_State = 0; // 関数内でメンバ変数を定義if ( m_State == 0 ){ m_State = 1; }
    }
};

マクロとstd::mapを使うことでそれっぽく実装できました。

#include <stdio.h>#include <map>#define LOCAL_VAR_DECL( type, var_name ) static std::map<void*, type> var_name#define LOCAL_VAR_INIT( var_name ) if ( var_name.find( this ) == var_name.end() ) var_name[this]#define LOCAL_VAR_DEF( type, var_name, init ) LOCAL_VAR_DECL( type, var_name ); LOCAL_VAR_INIT( var_name ) = init#define LOCAL_VAR( var_name ) var_name[this]class A
{
public:
    void Func( void )
    {
        LOCAL_VAR_DEF( int, m_State, 0 ); // 関数内でメンバ変数を定義       #define m_State LOCAL_VAR( m_State ) // m_State でアクセスできるようdefineしておくswitch( m_State )
        {
            case0:
            {
                printf( "m_State = 0\n" );
                m_State = 1;
                break;
            }
            case1:
            {
                printf( "m_State = 1\n" );
                m_State = 0;
                break;
            }
        }
    }
};

https://ideone.com/C1rUV5

std::map<void*,型>でthisポインタをキーにしてアクセスすることで、メンバ変数のようなものを実現しています。std::mapの変数を関数内のstatic変数として定義しているので他の関数からは参照できませんが、関数外でLOCAL_VAR_DEFを使えば他の関数からも利用できると思います。

ところで、この実装は問題点がたくさんあります。

  • インスタンスが破棄されたあとも、動的メンバ変数データが残り続けてしまう。
  • インスタンスが破棄されたあとに、同じアドレスに同じクラスのインスタンスが生成された場合に、前のインスタンスのデータを参照してしまう。
  • アクセスするたびに、mapを検索するので効率が悪い。

このように致命的な問題があるので、実際に動くコードでは使ってはいけません。

ではどこで使うのかというと、以下の様な場面では役に立つかもしれません。

  • デバッグなどで一時的にメンバ変数を追加したい場合
  • メンバ関数の実装中に新しいメンバ変数が欲しくなったときに、とりあえずその場でメンバ変数を定義したい場合(あとで正式にメンバ変数を定義する)

どちらにしてもリリース版には残さないようにしましょう。

【ゲームプログラミング】ビット演算による点滅処理

$
0
0

ゲームプログラミングキャラクターを点滅させたいとき、以下のような実装にするかもしれません。

// C++class Player
{
    int m_DrawCount;
public:
    Player() : m_DrawCount( 0 ){}
    void Draw( void )
    {
        // 2フレームに1回描画if ( m_DrawCount % 2 == 0 )
        {
            // 実際の描画処理
        }
        ++m_DrawCount;
    }
};

2で割ったあまりが0の場合のみ描画してます。2で割ったあまりは0か1になるので、2フレームに1回描画することになります。 この実装でも特に問題はないのですが、ビット演算(&)を使うと効率的かつバラエティに富んだ点滅をさせることができます。

// C++class Player
{
    int m_DrawCount;
    uint8_t m_BlinkMask;
public:
    Player() : m_DrawCount( 0 ), m_BlinkMask( 1 ){}
    void Draw( void )
    {
        // 一致するビットが1つでもある場合描画if ( m_DrawCount & m_BlinkMask )
        {
            // 実際の描画処理
        }
        ++m_DrawCount;
    }
};

m_BlinkMaskを1にすると2フレームに1回の描画になります。しかも割り算が発生しない分高速です。

  • 0フレーム目 : 0 & 1 -> 0b0000 & 0b0001 -> 0b0000 ->偽 ->描画なし
  • 1フレーム目 : 1 & 1 -> 0b0001 & 0b0001 -> 0b0001 ->真 ->描画あり
  • 2フレーム目 : 2 & 1 -> 0b0010 & 0b0001 -> 0b0000 ->偽 ->描画なし
  • 3フレーム目 : 3 & 1 -> 0b0011 & 0b0001 -> 0b0001 ->真 ->描画あり
  • 4フレーム目 : 4 & 1 -> 0b0100 & 0b0001 -> 0b0000 ->偽 ->描画なし
  • 5フレーム目 : 5 & 1 -> 0b0101 & 0b0001 -> 0b0001 ->真 ->描画あり

m_BlinkMaskを変えるとさまざまなパターンの点滅になっていきます。

例えば、m_BlinkMaskを2にすると以下のように2フレームごとに描画のon/offが切り替わります。

  • 0フレーム目 : 0 & 2 -> 0b0000 & 0b0010 -> 0b0000 ->偽 ->描画なし
  • 1フレーム目 : 1 & 2 -> 0b0001 & 0b0010 -> 0b0000 ->偽 ->描画なし
  • 2フレーム目 : 2 & 2 -> 0b0010 & 0b0010 -> 0b0010 ->真 ->描画あり
  • 3フレーム目 : 3 & 2 -> 0b0011 & 0b0010 -> 0b0010 ->真 ->描画あり
  • 4フレーム目 : 4 & 2 -> 0b0100 & 0b0010 -> 0b0000 ->偽 ->描画なし
  • 5フレーム目 : 5 & 2 -> 0b0101 & 0b0010 -> 0b0000 ->偽 ->描画なし
  • 6フレーム目 : 6 & 2 -> 0b0110 & 0b0010 -> 0b0010 ->真 ->描画あり
  • 7フレーム目 : 7 & 2 -> 0b0111 & 0b0010 -> 0b0010 ->真 ->描画あり

ちなみに、このように同じ割合でon/offを繰り返すのはm_BlinkMaskの値を2のn乗にしたときのみで、他の場合はonの割合が多くなります。

m_BlinkMaskの値を63まで試したGif動画を用意したので、参考にどうぞ。

on/offを逆にしたバージョン

Macで60fpsの画面キャプチャGIF動画を作成する方法

$
0
0

普段、MacでのGif動画用の画面キャプチャはLICEcapというソフトを使っていますが、このソフトで画面キャプチャを行うとフレームレートが30fpsくらいになってしまいます。たいていは30fpsもあれば十分なのですが、ゲームの動画など60fpsにしたい場合もあります。

最近、60fpsの画面キャプチャGIF動画を作る必要があったのですが、Macでやる方法がなかなか見つからなかったのでまとめてみます。

手順

  • QuickTimePlayerで画面キャプチャを行う -> movファイル
  • InstagifferでmovファイルをGifファイルへ変換する

QuickTimePlayerの画面収録機能を使うと、60fpsで画面キャプチャできます。ファイルはmov(H.264)形式になります。そしてこの動画をInstagifferを使ってGif動画へと変換します。

Instagifferで60fpsGIF動画へ変換する

InstagifferはYoutubeからダウンロードしてGifへ変換する機能がメインのようですが、手持ちのファイルを変換することもできます。

Instagifferは動画の尺を変更したりキャプションを入れたりエフェクトを掛けたりできるようですが、単純に動画を変換したいだけの場合、逆にそれらを使ってしまわないように注意が必要です。

Instagifferを使って動画をそのまま変換するための手順です。

  • 動画ファイルパスを入力する
  • Lengthを0にする(0にすると尺変更なし)
  • fpsを60にする
  • Qualityを100にする
  • エフェクトウインドウを表示してEnhanceを無効にする
  • 「Create GIF!」ボタンを押してGifを生成する

不要なフレーム(コマ)を削除したい場合は、右側のプレビュー画面でフレームを選択しダブルクリックで削除することができます。

おまけ

LICEcapで作成した画面キャプチャGIF(30fps)

QuickTimePlayer->Instagifferで作成した画面キャプチャGIF(60fps)

おまけ2

他に試したツール。どちらもキャプチャではなく変換ソフト。

  • PicGIF Lite : 60fpsで生成されませんでした。
  • Gifrocket : 60fpsで生成されたけど、画質が劣化。と思いきや、デフォルト設定でリサイズされていただけだった。リサイズしないように設定したら綺麗にでた。悪くないかも。
Viewing all 120 articles
Browse latest View live