lispbuilder-sdl on mac
どうせなのでもっとアクションゲーム的なのを作ったりできないかなーと思ってlispbuilder-sdlを入れてみた。前何回か入れようとして挫折しているが、今回はとりあえず例を実行出来るところまでは行った。
環境:
先に書いておくとclispではまだ成功していない。逆にclozure clでは2段下のトラップのマスクがなくても大丈夫だった。
libsdl
macportsで libsdl-framework を入れておく。
$ sudo port install libsdl-framework $ sudo port install libsdl_{gfx,image,mixer,net,ttf}-framework # libsdl_sound-frameworkが入らないが今回は追求していない # -> これっぽい: https://trac.macports.org/ticket/39798
lispbuilderライブラリ本体
(ql:quickload :lispbuilder-sdl)
を実行すると途中でライブラリがないと言われるので。
~/quicklisp/dists/quicklisp/software/lispbuilder-なんとか/lispbuilder-sdl/cocoahelper
に行ってmakeしてリトライする。なんとかの部分は一番新しい日付のもの。*1
(ql:quickload :lispbuilder-sdl-examples) (lispbuilder-sdl-examples:mandelbrot)
とするとマンデルブロ集合が出てくる…はずなのだが floating point inexact とか言って怒られる。これについてぐぐってもどうも情報を見つけられないのだが、どうもsbclがC言語で普通例外として扱わない*2 FE_INEXACT、common lispでは FLOATING-POINT-INEXACT をトラップするようにしているからのような気がする。
sb-int:with-float-traps-masked を使うとこのトラップを一時的に無効化出来るようなのだが、
(sb-int:with-float-traps-masked
(:inexact) (sdl-examples:mandelbrot))
としてもエラーが出る。
この辺の扱いはsbclだと
CL-USER> (sb-int:get-floating-point-modes)
(:TRAPS (:OVERFLOW :INVALID :DIVIDE-BY-ZERO) :ROUNDING-MODE :NEAREST
:CURRENT-EXCEPTIONS (:INEXACT) :ACCRUED-EXCEPTIONS (:INEXACT) :FAST-MODE NIL)
と取り出せるぽい?で
CL-USER> (sb-int:set-floating-point-modes
:traps '(:overflow :divide-by-zero))
; No value
CL-USER> (sb-int:get-floating-point-modes)
(:TRAPS (:OVERFLOW :DIVIDE-BY-ZERO) :ROUNDING-MODE :NEAREST :CURRENT-EXCEPTIONS
(:INEXACT) :ACCRUED-EXCEPTIONS (:INEXACT) :FAST-MODE NIL)
とすると設定出来て、この状態だと(lispbuilder-sdl-examples:mandelbrot)がちゃんと動いて見える。
common lispの規格ではFLOATING-POINT-INEXACTがトラップされるかどうかは処理系依存という事になっている。昔は起きなかったということは最近のsbclのどこかのバージョンでこのへんの初期値が変わったのかな?
(sb-int:with-float-traps-masked (:invalid) (sdl-examples:mandelbrot))
でいいようだ。
clozure cl 1.8.1 ではこういうことをしなくてもいけた。clispはちょっと色々やってみたがまだ成功していない。こういう目的だとどっちにしろclispだと荷が重いかもしれないが。
14/5/14追記: slimeとの相性について
この記事を見たような気もする質問をstackoverflowで見かけ、回答したのでこちらにも追記しておく。
sbcl+slimeでlispbuilderやopengl系のライブラリのデモを実行すると、表示されるwindowのタイトルバーが表示されて操作も効かない、のような症状が現れる。
slimeがemacsとlispの間の通信に用いている方式はいくつかあって swank:*communication-style* で設定でき 、デフォルトでは :spawn となっているのだが、このデフォルトの方式が何らかの理由でうまくいっていないのだろう。
~/.swank.lisp に
(setf swank:*communication-style* :fd-handler)
と記述しておくと正常に動いてくれる。しかしこれはバグなのだろうか?*3
link
- sb-int:set-floating-point-modes https://github.com/sbcl/sbcl/blob/sbcl-1.2.2/src/code/float-trap.lisp#L58
- sb-int:get-floating-point-modes (上の125行目)
おまけ
とりあえず定番のライフゲームを作ってみた
(require :lispbuilder-sdl) (defparameter *board-size* 100) (defparameter *life-initial-ratio* 0.3) (defparameter *board* nil) (defun make-board () (loop for x below *board-size* collect (loop for y below *board-size* collect (< (random 1.0) *life-initial-ratio*)))) (defun init-board () (setf *board* (make-board))) (defun count-neighbors (board x y) (count-if #'identity (mapcar (lambda (dx dy) (elt (elt board (mod (+ y dy) *board-size*)) (mod (+ x dx) *board-size*))) '(1 1 1 0 0 -1 -1 -1) '(1 0 -1 1 -1 1 0 -1)))) (defun alivep (board x y) (elt (elt board y) x)) (defun step-generation (board) (loop for y below *board-size* collect (loop for x below *board-size* collect (if (alivep board x y) (member (count-neighbors board x y) '(2 3)) (eq (count-neighbors board x y) 3))))) (defun life () (setf *board* (make-board)) (let ((scale 2)) (sdl:with-init () (sdl:window 200 200 :title-caption "life") (setf (sdl:frame-rate) 10) (sdl:with-events () (:quit-event () t) (:key-down-event () (sdl:push-quit-event)) (:idle () (sdl:clear-display sdl:*black*) (loop for x below *board-size* do (loop for y below *board-size* when (alivep *board* x y) do (sdl:draw-box (sdl:rectangle :x (* scale x) :y (* scale y) :w scale :h scale) :color sdl:*white*))) (setf *board* (step-generation *board*)) (sdl:update-display)))))) #+sbcl (sb-int:with-float-traps-masked (:invalid) (life)) #-sbcl (life)
8/24 23:11 sbclしか考えてなかったのでちょっと修正
2:05 インストールするべきライブラリを追加
2016/04/13 コマンド内容にいくつか間違いがあったので修正、roswellについて追記
*1: roswellを使う場合のパスは .roswell/impls/ALL/ALL/quicklisp/dists/quicklisp/software/lispbuilder-なんとか/lispbuilder-sdl/cocoahelper になる
*2:ref: http://ksmakoto.hatenadiary.com/entry/2012/03/27/103513
*3:参考: https://code.google.com/p/lispbuilder/wiki/DownloadInstallation , http://stackoverflow.com/questions/18345464/cl-opengl-under-slime-on-macos-crashing-sbcl-bug
Land of Lispを読んだ
長い事積んでたけど最近の週末を使ってようやく読み終えられた。日本語訳の方。
下の変なPV(プログラミングの本のPV!)通りぶっ飛んでて有名なlispの本。
公式ページも漫画になっていて、雰囲気がわかる。
http://landoflisp.com/
いろんなゲームを作って行き、その過程で色々なテクニックを学んで行けるようになっている。最終的には実用的なDSLを作ったりwebサーバを立ててみたり、遅延評価のライブラリを組んだ上にゲームAIを作ったりと盛りだくさん。
ゲームといってもテキストインタフェースのわりと地味なやつが多いのだが、これが結構ちゃんと面白いので作っててうれしいし先へ進むモチベーションが湧く。
後半ではAIとの対戦ができる陣取りゲームも作るのだが、ゲームAIは触れた事がなかったのでとても面白かった。遅延評価を使うとゲーム木という概念をすっきり扱えて、ゲーム木からスコアを計算する事ができればAIのコードがほとんど自明なものになってしまうのね。最初はテキストインタフェースなんだけど最終的にはブラウザ上で操作が完結するグラフィカルなゲームになってうれしかった。
しかしアクセッサみたいなやつを定義しないでリストをそのまま生で扱ったり*1、グローバル(違)変数をわりとカジュアルに使ったり*2、僕がこのへん真似すると簡単にエンバグするかもしれない。BASIC全盛期とかに本に載ってたゲームのコードもこういう感じだったのだろうか。
後半だんだん大変になっていった(特に最後の3章くらい)が示唆に富んだ楽しい本だった。川合さんの訳もよかった。
個人的な雑多なこと
- 文字列やら配列やらなんでもかんでもlistにするの、効率悪そうに思って今まで半ば生理的に避けていたのだが、早すぎる最適化だったのだろう。リスト処理に特化している言語なのだからなるべくリストの形で扱う事を考えるべきだ。まとも(?)な本読む前に数値計算に使ったりしたせいで変な癖が付いたかも。
- 読んでる間ずっと http://www.clisp.org が落ちていて(というかbandwidth limitを超えたため遮断となっていて)なんだこれとなった。 http://www.gnu.org/software/clisp/ は繋がることに最後の方で気付いた。
- (readtable-case *readtable*)を:invertにしたときのsymbol-nameの挙動どうかしていると思ってた*3のだけど、多分普通はprincとか使うのだろう。
- lazyなユーティリティを書いて前のコードをそれで書き直すの割りと非人間的作業感がある。それ以上のリターンはあったけれども。
オープンソースのパスワード管理ソフトpassを導入したメモ
パスワードは適当な生成規則を作って運用していたのだが、サービスによっては長さが足りなかったり記号に対する指定が色々あったりで対応出来なくなってきたので、パスワード管理ソフトをついに導入する事にした。
無料のよりは有料の方が安心出来るかな、とか思いながら適当に探していたが、そういえばオープンソースにそういうのがあるに違いないな、と思ったらやはりあった。
pass というやつで、gpgでパスワードを暗号化してローカルで管理するCLIベースの管理ソフト。
使い方
上の本家から引用すると、まず保存しているパスワードは
zx2c4@laptop ~ $ pass Password Store ├── Business │ ├── some-silly-business-site.com │ └── another-business-site.net ├── Email │ ├── donenfeld.com │ └── zx2c4.com └── France ├── bank ├── freebox └── mobilephone
というように一覧出来る。
zx2c4@laptop ~ $ pass Email/zx2c4.com sup3rh4x3rizmynam3
としてパスワードを表示したり、またはターミナルに印字するのがいやな場合は
zx2c4@laptop ~ $ pass -c Email/zx2c4.com Copied Email/jason@zx2c4.com to clipboard. Will clear in 45 seconds.
というようにクリップボードに直接送る事もできる。
登録は
zx2c4@laptop ~ $ pass insert Business/cheese-whiz-factory
とする。自動生成もできて
zx2c4@laptop ~ $ pass generate Email/jasondonenfeld.com 15 The generated password to Email/jasondonenfeld.com is: $(-QF&Q=IN2nFBx
という感じ。
詳しくは man page 参照。
インストール
僕はmacportsを使っているので
$ sudo port install pass
とした。homebrewの場合は
$ brew install pass
でいけるらしい。色々なlinuxのディストリビューションでもパッケージになってるのでyumとかapt-getでインストール出来るようだ。
セットアップ
gpgについて
passはgpg2を暗号化に利用しているので、まずこいつをセットアップする必要がある。
まず鍵を作る:
$ gpg2 --gen-key
色々と聞かれるので答えて行く。
名前などを入力したあたりで
You selected this USER-ID: "nos"
ユーザーIDが表示されるのでメモっておく。
pass 初期化
暗号化に使うためさっきのgpgユーザーIDを指定する
$ pass init "nos"
お好みでgitもセットアップしておくとパスワードなどの変更の度にcommitされる:
$ pass git init
gpg-agent
gpgの鍵を作成した時のパスフレーズを毎回入れるのは面倒という時は、以下を ~/.bash_profile に書いておくと最初に一回打てばあとはgpg-agentが肩代わりしてくれる。
# from https://blog.kumina.nl/2009/11/gpg-agent-on-macosx/ if test -f $HOME/.gpg-agent-info && kill -0 `cut -d: -f 2 $HOME/.gpg-agent-info` 2>/dev/null; then GPG_AGENT_INFO=`cat $HOME/.gpg-agent-info` export GPG_AGENT_INFO else eval `gpg-agent --daemon` echo $GPG_AGENT_INFO >$HOME/.gpg-agent-info fi
似たような文字列を含んだデータをまとめる
説明しにくいんだけど、日付とデータが一行に入ってるデータファイルがあるとき、データの方だけ合計を取って、日付の共通部分は残しておきたい、みたいなことを考えた。
つまり、
#date time count time 2013/03/12 08:23:34 12 0.21 2013/03/15 08:40:20 13 0.22 2013/03/19 12:23:34 22 0.21 2013/03/23 00:41:44 12 0.20
みたいなデータを食べさせると
2013/03/XX XX:XXXXX 59 0.84
みたいな出力をしてほしい。
pythonでそういうスクリプトを書いた。各データについてまず整数として、次に浮動小数点としてパースを試みて、どっちもだめなら文字列と見なして、それらの共通部分を作る。
python2.1から入っているシーケンスの比較を行う組み込みライブラリである difflib を使った。
sum_rows.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # sum_rows.py import difflib def try_convert_into_number(s): """try convert s into integer. if could not try convert into float. then give up (return None)""" try: return int(s) except ValueError: try: return float(s) except ValueError: return None def make_diff_to_XXX(s1,s2): """compare two strings s1 and s2 then return string marked with 'X' where s1 differs from s2""" s = difflib.SequenceMatcher(a=s1, b=s2) str='' back = 0 for block in s.get_matching_blocks(): # print "match at a[%d] and b[%d] of length %d" % block if block[0] != back: str = str + 'X' * (block[0]-back) str = str + s1[block[0]:block[0]+block[2]] back = block[0]+block[2] return str # make_diff_to_XXX("This is a pen", "This is a pan") def nonempty_lines(stream): """yield lines that is neither comment (begins by #) nor empty""" comment_char = '#' for l in stream: if l == "" or l[0] == comment_char: continue l = l.rstrip() yield l def sum_rows(stream): """sum numbers in each columns from lines in stream. raises error when a column contains both number and string.""" hist = [] max_cols=0 for l in nonempty_lines(stream): words = l.split() max_cols = max(max_cols, len(words)) if(max_cols>len(hist)): hist.extend([None]*(max_cols-len(hist))) for i, w in enumerate(words): num = try_convert_into_number(w) if num != None: hist[i] = (hist[i] or 0) + num elif not hist[i]: hist[i] = w # comment-out 2 lines below for large file else: hist[i] = make_diff_to_XXX(hist[i], w) return hist import sys if __name__ == "__main__": if len(sys.argv) <= 1: print "usage: {0} file('-' for stdin)".format(sys.argv[0]) exit(1) fin = sys.stdin if (sys.argv[1] == '-') else open(sys.argv[1]) hist = sum_rows(fin) for i in range(len(hist)): print hist[i], print
僕は
$ sum_rows datafile
とか
$ grep ^2014/05/ datafile | sum_rows -
みたいな感じで使っている。
boost::spirit::qi を触ってみる
C++における文法解析ライブラリとしてboost::spiritというものがある。BNF記法に非常に近い見た目の(しかもちゃんと動く) c++のコードとして文法を書き下せるという、c++の限界に挑戦している感のあるライブラリである。マニュアルを読む(ほぼqiのところしか読んでないけど)のに大分時間をかけてしまったので備忘録を兼ねて記事にしてみる。
boost::spiritは大きく3つのモジュールに分かれていて、定義した文法に沿って文字列をデータに変換するqi、逆にデータから文字列をつくるkarma、また字句解析に特化したlexからなっている。今回はその中のqiを触ってみる。
BNF記法とspirit
BNF記法(あるいはEBNF記法)というのは、
group ::= '(' expression ')' factor ::= integer | group term ::= factor (('*' factor) | ('/' factor))* expression ::= term (('+' term) | ('-' term))*
というやつ*1。
boost::spiritを使うとこれをC++コードとして(!!)
group = '(' >> expression >> ')'; factor = integer | group; term = factor >> *(('*' >> factor) | ('/' >> factor)); expression = term >> *(('+' >> term) | ('-' >> term));
のように書ける。*が前に来てたりとかくっつけるのに >> を使ってたりするくらいで、ほぼそのまま。
もともといくつかの定義が組み込まれていて、例えば int_ はintを、double_ は double を受ける事ができる。
std::string input = "123, 456.789"; std::pair<int, double> p; qi::parse(input.begin(), input.end(), '(' >> qi::int_ >> ", " >> qi::double_ >> ')', // <- ここが定義 p);
とすると p.first に 123、 p.second に 456.789 が格納されるという具合。
単純な論理式: 文法の定義と rule の定義
今回は練習として、 (T v F) とか (F v (F v T)) みたいな論理式を与えると結果を T か F で返してくるものを作ってみた。
文法の定義は
constant ::= 'T' | 'F' formula ::= constant | '(' formula 'v' formula ')' |'(' formula '^' formula ')'
としておく。括弧の付け方に曖昧さのない定義*2。
この定義の一行に相当するのをboost::spiritではruleといい、ruleをまとめたものをgrammarという。
上の定義の1行目は spirit では
constant = lit('T') | lit('F');
セマンティックアクションで値や処理を指定
読み込んだらboolとして値を得たい、というときにはセマンティックアクションというものを使う。[]演算子がオーバーロードされていて、この中に処理を書く。今の定義にセマンティックアクションを加えると次のようになる:
constant = lit('T')[_val = true] | lit('F')[_val = false];
ちょっと黒魔術めいてきたけれども、_valというのがパース結果のための変数にあたる。これはboost::lambdaカスタム仕様みたいなライブラリであるphoenixの一員。他にも例えばパーサが別の子パーサの組み合わせからなっている時に子パーサの結果を _1 とか _2 とかで受けるとかできる。phoenixの式でなくて関数ポインタとか関数オブジェクト、boost::lambdaの式なんかを与える事もできる。値を指定するのに限らず外の変数を書き換えたりとか色々可能。
rule/grammar の attribute
パース結果の型はどう扱われているのか気になるかもしれない。boost::qiではこの型をsynthesized attributeといっている。attributeというのは、qiのマニュアルを読む限りでは似たような型をまとめたものと思っていいと思う(例えばvector
qi::rule<Iterator, int(double)>
テンプレート引数の2番目がそれで、関数と同じような形式の宣言になっているのがわかると思う*4。今の constant は bool() という形の宣言をする事になる。
あと宣言としては、パース時に空白を適宜スキップしたいときはもう一つ引数が加わって
qi::rule<Iterator, int(double), qi::space_type>
*5
という形になる。grammarでも同じ。
2行目。
formula = (constant [_val = _1] | ('(' >> formula >> 'v' >> formula >> ')')[_val = _1 || _2] | ('(' >> formula >> '^' >> formula >> ')')[_val = _1 && _2]);
早速 _1、_2 を使っている。こうやってそれぞれ1つ目と2つ目のパーサの結果を受けて計算するという事ができる*6。
grammarの定義
この2つをまとめるのにgrammarというのを定義する事になる。手順としては、
- qi::grammar
を継承して構造体を定義。Iteratorはテンプレートにする。 - メンバ変数としてrule群を宣言
- コンストラクタでbase_typeを一番最初に来るruleで初期化する
- コンストラクタ内で各ruleを定義していく
という感じになる。
template<typename Iterator> struct bool_grammar : qi::grammar<Iterator, bool(), ascii::space_type>{ bool_grammar() : bool_grammar::base_type(formula){ // formulaから開始 using qi::lit; constant = (lit('T')[_val = true] | lit('F')[_val = false]); formula = (constant [_val = _1] | ('(' >> formula >> 'v' >> formula >> ')')[_val = _1 || _2] | ('(' >> formula >> '^' >> formula >> ')')[_val = _1 && _2]); } qi::rule<Iterator, bool(), ascii::space_type> constant, formula; };
使ってみる
こうして定義したgrammarを、phrase_parse (空白を読み飛ばす場合)なり parse (読み飛ばさない場合)なりに渡す事でパースを行う。
今回は空白を許容するためphrase_parseの方を使う。入力の範囲を表すイテレータ2つと パース用rule/grammar、読み飛ばすべき文字を表すrule/grammar を渡す。結局全体としては次のようになる:
#include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> using namespace boost::spirit; using namespace boost; template<typename Iterator> struct bool_grammar : qi::grammar<Iterator, bool(), ascii::space_type>{ bool_grammar() : bool_grammar::base_type(formula){ // formulaから開始 using qi::lit; constant = (lit('T')[_val = true] | lit('F')[_val = false]); formula = (constant [_val = _1] | ('(' >> formula >> 'v' >> formula >> ')')[_val = _1 || _2] | ('(' >> formula >> '^' >> formula >> ')')[_val = _1 && _2]); } qi::rule<Iterator, bool(), ascii::space_type> constant, formula; }; #include <iostream> #include <string> using namespace std; int main(){ string buf; bool result; bool_grammar<string::const_iterator> grammar; while(getline(cin, buf)){ string::const_iterator iter = buf.begin(), end = buf.end(); bool success = qi::phrase_parse(iter, end, grammar, ascii::space, result); if(success && iter == end){ // パース成功かつ残り物がない cout << " => " << (result ? 'T' : 'F') << endl; }else{ cout << " => parse failed" << endl; } } return 0; }
コンパイルして実行すると入力待ちになり、論理式を入力すると都度パースして計算結果を表示する。ctrl+dで終了。
$ g++ -I/usr/local/include bool.cc -o bool $ ./bool T => T F => F abc => parse failed F v F => parse failed (F v F) => F (T ^ F) => F (T v F) => T (F v (F v T)) => T
定義してない単語を入れたり括弧の付け方が間違っているとちゃんとはじかれる。
強力さ、弱み
このように40行足らずで入力をパースしてブール演算をするプログラムが書ける。公式のチュートリアルを見るとxmlパーサなどもデータ構造などを含めてなかなかきれいに書けるのがわかって面白い。
上のコードだと読み飛ばしにspirit::asciiを使っているが、これはutf-8を想定していない(クラッシュする)ので、そういうのを読ませたい時は ascii::*7 となっているところを全部 standard_wide:: にするとよいようだ*8 *9。
というわけでとても面白いライブラリだけど、欠点として非常にコンパイル時間がかかること(この小さな例でもmac book pro retina 2012モデルで11秒弱かかる)、エラーメッセージが膨大になることがある。この辺との付き合い方については http://sssslide.com/www.slideshare.net/yak1ex/impractical-introduction-ofboostspiritqi-pdf の"蛇足"を見るとよいと思う。
link
let's boostの記事 http://www.kmonos.net/alang/boost/classes/spirit.html
マニュアル(英語) http://www.boost.org/doc/libs/1_55_0/libs/spirit/doc/html/index.html
*1:こういうように書けるものを文脈自由言語という。正規言語がオートマトンで受理出来るものなのに対して、正規言語はオートマトンにスタックを1つ加えたもので受理出来るものをいう
*2:曖昧さのある文法の時は、|がショートサーキット的に働くのに注意。つまりa|bでaにもbにも当てはまる場合bは試みられない。またしばらく後で失敗した時のバックトラック対象にもならないようだ。
*3:オーバーロードのおかげで左右かたっぽがruleやgrammarだったりするとlitは省略出来る。全部省略は出来ないあたりがc++の型推論の限界なのかもしれないが
*4:1番目は色々な入力をとれるようにテンプレートにしてある
*5:この宣言がない時はqi::parse、あるときは qi::phrase_parse を使う事になる ref: http://slashdot.jp/journal/524079/Boost-Spirit-Qi-の-rule-と-Skipper-の有無について http://d.hatena.ne.jp/sorayukinoyume/20121113/1352810975
*6:'('みたいなのも厳密にはパーサなのだが、synthesized attributeがUnusedというものになっているのでその結果はいなかったことになる
*7:正確には boost::spirit::ascii::
*8:http://sourceforge.net/mailarchive/message.php?msg_id=24449761 、 http://www.boost.org/doc/libs/1_55_0/libs/spirit/doc/html/spirit/qi/reference/char/char_class.html
*9:クラッシュしさえしなければいいのではなくパーサそのものもutf-8を想定したい場合はやったことないけど http://stackoverflow.com/questions/13679669/how-to-use-boostspirit-to-parse-utf-8 あたりを参考にすればいいのかも
emacsでファイルをインデントするスクリプト
emacsでサンプルとか他人からもらったファイルをいじって行くとき、ファイルのインデントの流儀がemacsと違うとTabを押したときに残念な気分になる。下のファイルに実行権限をつけてPATHの通ったところにおいてやると
$ emacs-format-file *.cc
とすることでインデントが揃う。
参考urlによればこの書き方(最初2行と最後1行)でmac、linux、windowsのどれでも動くらしい。とりあえずmacとlinuxでは動いている。
参考
- http://www.emacswiki.org/emacs/EmacsScripts#toc3
- http://www.cslab.pepperdine.edu/warford/BatchIndentationEmacs.html
emacs-format-file:
:;exec emacs -batch -l "$0" -f : "$@" --no-site-file -q # -*- Emacs-Lisp -*- ; @emacs -batch -l "%~f0" -f : %* --no-site-file -q & goto :EOF ; indent specified file(s) with emacs indent-region ; http://www.emacswiki.org/emacs/EmacsScripts ; http://www.cslab.pepperdine.edu/warford/BatchIndentationEmacs.html (defun : () (dolist (f (nthcdr 5 command-line-args)) (find-file f) ;; (c-set-style "stroustrup") (indent-region (point-min) (point-max) nil) (untabify (point-min) (point-max)) (save-buffer)) ) ;:EOF