Pythonで作る奇妙なプログラミング言語 その1(HQ9+)

二ヶ月ほど放置しておりました。
その間何もやってなかったわけではないです。主にGoogle App Engineまわりで色々やってました。

  • Django-nonrelを使ったGAEでのDjangoアプリ作成
  • 上記環境でTask Queueを使う
  • 上記2つを組み合わせて作った「TLに一年前の自分を再現するbot

とかここに書くネタはあるんですが、ええと、そのうち書きます。

そんなことより、ちょいとRubyを勉強しなきゃいけない事情がありまして、1年ほど前に買った

Rubyで作る奇妙なプログラミング言語 ~Esoteric Language~

Rubyで作る奇妙なプログラミング言語 ~Esoteric Language~

をやってます。
この本は、Hello, world!を表示するのにわざわざ

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
------------.<++++++++.--------.+++.------.--------.>+.

と書かなければいけない、お馴染みBrainf*ckに代表されるような

「奇妙な言語(Esoteric Language)」の処理系を実装しながら、プログラミング言語がどのようにできているのか、その秘密に迫っていく。最後まで読み終えれば、きっと自分でも新しいプログラミング言語を作れるようになっているだろう。

というものです。
買った直後に半分ぐらい読みましたが、かなり面白いですね。コードのレベル自体はそんなに高くないので、入門書を読んだばかりの初心者でもついていけると思いますし、初心者から中級者への橋渡しに丁度いいんじゃないかと思います。
再びこれを読んで勉強しているところですが、折角なのでここに出てるプログラムをPythonに移植して、RubyPythonの特徴を比べてみようじゃないか、ということでやってみたいと思います。

その第1弾が今回のHQ9+です。
HQ9+ - Wikipedia

HQ9+はクリフ・ビッフルによって作られたジョーク向け難解プログラミング言語である。実用言語ではない。
...

  • Hコマンドは文字列"Hello, world!"を出力する。
  • Qコマンドはプログラムのソースコードを出力する(参考:クワイン_(プログラミング))。
  • 9コマンドは『99 Bottles of Beer』(アメリカの数え歌で、プログラミングの例題でよく利用される)の歌詞を出力する。
  • +コマンドはアキュムレータをインクリメント(1だけ増やす)する。

というたった4つの命令からなる実用性ゼロの言語ですが、これからもっとイカれた言語を実装していくための準備体操としてはまずまずでしょう。

hq9.py

import sys

class HQ9Plus():
    
    def __init__(self, src):
        self._src = src
        self._count = 0
    
    def run(self):
        for c in self._src:
            if c == 'H':
                self._hello()
            if c == 'Q':
                self._print_source()
            if c == '9':
                self._print_99bottles_of_beer()
            if c == '+':
                self._increment()
    
    def _hello(self):
        print 'Hello, world!'
    
    def _print_source(self):
        print self._src
    
    def _print_99bottles_of_beer(self):
        for i in range(99, -1, -1):
            if i == 0:
                before = 'no more bottles'
                after = '99 bottles'
            elif i == 1:
                before = '1 bottle'
                after = 'no more bottle'
            elif i == 2:
                before = '2 bottles'
                after = '1 bottle'
            else:
                before = '%d bottles' % i
                after = '%d bottles' % (i-1)
            
            if i == 0:
                action = 'Go to the store and buy some more'
            else:
                action = 'Take one down and pass it around'
            
            print '%s of beer on the wall, %s of beer.' % (before.capitalize(), before)
            print '%s, %s of beer on the wall' % (action, after)
            if not i == 0: print ''
    
    def _increment(self):
        self._count += 1

try:
    src = open(sys.argv[1]).read()
except IOError:
    src = sys.argv[1]
except IndexError:
    src = raw_input()

hq9plus = HQ9Plus(src)
hq9plus.run()

できました。早速動かしてみましょう。入力はファイルから、コマンドライン引数から、標準入力からの3つに対応していますが、ここではファイルを読み込ませます。ソースコード

h.hq9

H

とってもシンプル!!

C:\hogehoge>python hq9.py h.hq9
Hello, world!

動きました。「ハロワ表示させただけじぇえねか馬鹿にしてんのか!」との声が聞こえてきそうですが仕様なので諦めてください。
Rubyとの比較で気付いた点をいくつか

  • やっぱりメソッド定義の時にいちいちself入力するの面倒臭い。一方Rubyは引数取らない場合はメソッド名に続く()を省略できる(呼び出し時も同様)けど、それはそれで変数呼び出しと勘違いしそう。
  • インスタンス変数やクラスメソッドを呼び出す時にself.foo()とselfつけるのも面倒臭い。Rubyインスタンス変数は先頭に@をつけるし、クラスメソッドは明示しなくてもいい。ただこれは一長一短ありそう。
  • RubyのARGFに相当するものはPythonにないのかな?同じ事を実装するのに例外処理とかしないといけないからやや面倒
  • ループについて、Rubyはfor, whileに加えてeach, times, upto, downtoと沢山ある。全てfor(たまにwhile)で処理するPythonの方がシンプルで好きかな

さて、このHQ9+はもちろん言語として使い物になりません。四則演算すらできませんし。次回は他のメジャーな言語と同等の機能を備える、チューリング完全な言語を実装します。
そう、冒頭にも挙げた、あのBrainf*ckです。