2012-04-29
Create Your Own Programming Language
読書, lexer, coffeescript, ruby, programming
以前から言語を作る事に興味がわいていたのですが、中々難しそうに見えて尻込みして挑戦出来ていませんでした。それが、昨日急に言語を作りたい衝動にかられて wikipedia 巡りをしてしまいました。最近盛り上がってきつつあり、Rails にも採用された CoffeeScript は自分の中でも感動した言語だったので当然 wikipedia を参照したわけです。
CoffeeScript は電子書籍である "Create Your Own Language" を読んで開発された。
上記引用の通り、電子書籍の Create Your Own Programming Language を読んで開発されたらしく、勢い勇んで洋書に手を出しました。もう勢いです。
以下のリンクから購入出来ます。
Create Your Own Programming Language
ページを開いていきなり "あなたが考えているより簡単に初めての言語を作れる" なんて謳い文句までついてくる始末。
英語も幸いにしてあまり難しくない表現で書かれているので僕程度の英語力でもなんとかなっています。問題は単語ですが、そこは iPad 様々ですかね。
全部で 160 ページくらいの薄い物なんで抵抗もあまり無く、(とは言っても未だ 30 ページ程度ですが。)読めているので
この本はすぐに最後まで読み切れそうです。
現在は Lexer を Ruby で書く部分です。今回の記事ではそのソースコードの解読をしておこうと思います。
lexer.rb
class Lexer # KEYWORDS では 言語内で用いる予約語を定義しているようです # TOY 言語に必要な機能のみを実装する事を目的にしているのであまり予約語は無いです KEYWORDS = ["def", "class", "if", "true", "false", "nil"] # tokenize ではトークンの生成を行なっています # code はプログラムのコードを受け取ります def tokenize(code) # 改行コード等 code の最後の特殊なコードの削除 code.chomp! i = 0 tokens = [] current_indent = 0 indent_stack = [] while i < code.size # i の挙動を見る為に僕が仕込んだコードです # 本来必要の無いコード p i # 残っている code の抜き出し chunk = code[i..-1] # トークンの生成 # String class の slice で regexp に最初にマッチする文字の抜き出し if identifier = chunk[/\A([a-z]\w*)/, 1] if KEYWORDS.include?(identifier) tokens << [identifier.upcase.to_sym, identifier] else tokens << [:IDENTIFIER, identifier] end # identifier のサイズ分だけ文字探査を進める i += identifier.size elsif constant = chunk[/\A([A-Z]\w*)/, 1] tokens << [:CONSTANT, constant] i += constant.size elsif number = chunk[/\A([0-9]+)/, 1] tokens << [:NUMBER, number.to_i] i += number.size elsif string = chunk[/\A"(.*?)"/, 1] tokens << [:STRING, string] i += string.size + 2 # : 以降の indent サイズのチェック(if, def等) elsif indent = chunk[/\A\:\n( +)/m, 1] if indent.size <= current_indent raise "Bad indent level, got #{indent.size} indents." + "expected > #{current_indent}" end current_indent = indent.size indent_stack.push(current_indent) tokens << [:INDENT, indent.size] i += indent.size + 2 # indent サイズのチェック elsif indent = chunk[/\A\n( *)/m, 1] if indent.size == current_indent tokens << [:NEWLINE, "\n"] elsif indent.size < current_indent while indent.size < current_indent indent_stack.pop current_indent = indent_stack.first || 0 tokens << [:DEDENT, indent.size] end tokens << [:NEWLINE, "\n"] else raise "Missing" end i += indent.size + 1 elsif operator = chunk[/\A(\|\||&&|==|!=|<=|>=)/, 1] tokens << [operator, operator] i += operator.size elsif chunk.match(/\A /) i += 1 else value = chunk[0, 1] tokens << [value, value] i += 1 end end while indent = indent_stack.pop tokens << [:DEDENT, indent_stack.first || 0] end tokens end end
コメントアウトで説明を入れては見ましたが、以外とコードを見れば分かる内容 && メンド(ry なので後半はあまりコメント入れてません。
lexer_test.rb
require "test/unit" require "lexer" class LexerTest < Test::Unit::TestCase def setup @code = <<-CODE if 1: print "..." if false: pass print "done!" print "The End" CODE @tokens = [ [:IF, "if"], [:NUMBER, 1], [:INDENT, 2], [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"], [:IF, "if"], [:FALSE, "false"], [:INDENT, 4], [:IDENTIFIER, "pass"], [:DEDENT, 2], [:NEWLINE, "\n"], [:IDENTIFIER, "print"], [:STRING, "done!"], [:DEDENT, 0], [:NEWLINE, "\n"], [:IDENTIFIER, "print"], [:STRING, "The End"] ] end def test_tokenize assert_equal @tokens, Lexer.new.tokenize(@code) end end
上記テストコードを以下の様に実行すると、
>|
$ ruby test/lexer_test.rb
Loaded suite test/lexer_test Started 0 2 3 4 8 13 14 19 22 24 25 30 36 40 43 48 49 56 57 62 63 . Finished in 0.003022 seconds. 1 tests, 1 assertions, 0 failures, 0 errorsこんな感じでテストが完了しました。 lexer_test.rb の中にある @tokens に本来出力されるはずの中身を書いているので、Lexer.new.tokenize(@code) の中身を目視でも確認しておくと理解が早まるのかなとも思いました。 tokenize の一行目で p i している為 i の挙動が表示されて何となくコードの流れがつかめたのは個人的に良かったかなと思ったり... 構文の解析はまだまだ先は長そうですが、この程度の長さのコードなら原理を学ぶのは出来そうです。 今回の Lexer の話はこれで終わりです。 それではまた本の中に戻ります。
2012-03-03
Rails っぽいコンフィグ
Linux, mysql, ruby, programming
前回 MySQL を Ruby から操作出来るようにしたので、今回は Rails のように開発版と本番の切り替えを簡単に行えるようにしました。
とは言え、サーバの動作時に切り替えるようなものではなく、スクリプトが動作するときに指定した設定で読むだけのものです。
db.yml
:usetype: :developement :developement: :database: db :host: host :user: user :pass: pass :production: :database: db :host: host :user: user :pass: pass
上記の設定ファイルを読みこんで、MySQL の設定を抜き出すようにしています。
require 'mysql' class SQL def initialize @list = YAML.load(File.read("db.yml")) end def type @list[@list[:usetype]] end end sql = SQL.new.type
これで、
db = Mysql::new(sql[:host], sql[:user], sql[:pass], sql[:database])
とすれば、db.yml の :usetype で指定した設定が引き出せます。
結構簡単に切り替えが出来るんですね。データベースの操作についてはまだ触り始めで勉強中なので今回はここまでにしておきます。
2012-02-29
Ruby on Rails 3 ポケットリファレンス
かなり遅くなりましたが、Wings さんより献本頂いた本を読み終えました。
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2012/01/26
- メディア: 単行本(ソフトカバー)
- 購入: 3人 クリック: 21回
- この商品を含むブログ (9件) を見る
Rails 3 の導入から始まり、各メソッドについて詳しく書かれています。ここからの入門としては開発の流れがこの本では分からないですが、Web にある情報とこの本だけでも十分に動くものを作れる内容です。
また、リファレンスとしての内容も非常に細かく解説を行われているため、そうそう困ることはなさそうです。
特に個人的にはテストの項目が詳しく、為になりました。なにせ、 Rails でのテストの作法とか良くわかりませんから。標準のライブラリである Unit だったのも個人的には○でした。いきなり他のを使うこと前提とか良くわかんないですし。それに古めの内容にはなるものの RSpec を使う場合の参考も載せているのである程度どちらも補完しているといえます。
付録にて解説されている CoffeeScript や SCSS についても基本的な説明を行われているので、これを機に触ってみようと言う人には十分な内容でした。
僕が CoffeeScript を勉強し始めたときは日本語の解説がなく不便でしたが、この辺の日本語化は最近早いですね。
2012-02-08
StumpWM 上でもうひとつ WM 動作
数カ月ぶりのブログ更新です。
ほんとは大学院の公聴会が終わったら直ぐに更新しようと思っていたんですが、面倒臭がってグダグダしてしまいました。
今回は StumpWM 上で GUI アプリをつくろうと思った時に Window が目一杯のサイズで出てくるのが鬱陶しかったため、開発用に WM を動かすことにしました。以前から目をつけていたんですが、やはりメンドクサクて放置。そして必要にかられて今回挑戦したわけです。
殆ど タイル型 window manager だけどウィンドウを float 制御したい という記事を使いました。異なる点といえば openbox を使用するように変更した事と、run-or-raise の恩恵をあずかるようにしたくらいです。
Xephyr, openbox 導入
$ sudo aptitude install xserver-xephyr openbox
floatwm.sh
#!/bin/sh ~/stumpwm/contrib/stumpish gnew openbox Xephyr -ac -br -fullscreen -host-cursor -class xephyr :1 & sleep 1 DISPLAY=:1 openbox &
kill_openbox.sh
#!/bin/sh ~/stumpwm/contrib/stumpish gselect openbox ~/stumpwm/contrib/stumpish gkill killall Xephyr
.stumpwmrc
(defcommand float-openbox () () (run-or-raise "/path/to/bin/floatwm.sh" '(:class "Xephyr"))) (defcommand kill-float-openbox() () (run-or-raise "/path/to/bin/kill_openbox.sh" '(:class "kill-openbox")))
ほんとに修正したところは少ないですが、コレでグループ切り替えしつつ openbox を起動できます。勿論 run-or-raise してるので 2 回目以降の float-window コマンドでは既に開いている openbox Group にフォーカスを移します。また、kill-float-window で openbox Group を Xephyr 毎切れます。


