近頃、 JavascriptでOreScript なんてのがちょっとはやっていたりしまして。

OreScript書くのにある程度ちゃんと動くパーサコンビネータがあれば便利かも、とおもったので以前書いたパーサコンビネータをいじってみました。

変更点

  • メソッド名などをHaskellにあわせた
  • よくもわるくも、記号含有率をあげた(and -> $に、or -> l に)
  • 相互再帰をサポートした
  • 左再帰(chainl1)をサポートした
  • 右再帰(chanr1)をサポートした

ということで、そこそこの用途に耐えるものになったと思います。

ダウンロード

完全にアンドキュメントです。すみません。ただ、ソースは200行くらいなんで見ればわかるかと。というか、HaskellのParsecのマニュアルを読めば基本一緒なので使い方がわかると思います。

ダウンロード

サンプル

id:amachangさんが書いていたような数式パーサ を定義してみましょう。

残念ながらLexerはつくっていないので、空白は受け入れられません。

var ExprParser = Inforno.Parsec.Parsers.define(function(){with(this){
  var mul = function(a,b){return a*b;};
  var div = function(a,b){return a/b;};
  var add = function(a,b){return a+b;};
  var sub = function(a,b){return a-b;};

  this.numbers =  many1( chrLike(function(c) { return (c >= '0' && c <= '9'); }) ) .ret(function(ns){
                    return parseInt(ns.join(""));
                  });
  this.expr = chainl1(p("term"), p("addop"));
  this.term = chainl1(p("factor"), p("mulop"));
  this.factor = (between(chr("("), p("expr"), chr(")"))) .l (numbers)
  this.mulop = (chr("*") .retval(mul)) .l (chr("/") .retval(div));
  this.addop = (chr("+") .retval(add)) .l (chr("-") .retval(sub));
}});

var test = "100*(100+200)/10";

var parser = new ExprParser(test);
var parseResult = parser.expr.parse();
if(parseResult.success()) {
  alert(parseResult.result);
  // => 3000
}

すごく、Parsecです・・・


こういうのを最近はやりの CodeRepos でやればいいのかなあ。


Pythonで自分用の小物アプリを結構書き溜めてるんですけど、実はそのほとんどにプラグインシステムみたいなのをつくってたりします。

たとえばファイルの整理自動化みたいなの。ファイルの移動前に処理を追加する、てな部分をプラグインにしてあるわけです。

まぁ機能的にはCPANのClass::Componentっぽいもんですね。ですがそこまで高機能なのはいらないので、シンプルに自分が必要な部分だけまとめてみました。

from inspect import getmembers, ismethod
import types

class Pluggable(object):
  def __init__(self):
    self.hooks = {}

  def load(self, *config):
    self.load_config(*config)
    self.load_plugins()

  def load_config(self, *config):
    self.config = []
    for i in config:
      if isinstance(i, ("".__class__, u"".__class__)):
        self.config.append((i, {}))
      else:
        self.config.append((i[0], len(i) > 1 and i[1] or {}))

  def load_plugins(self):
    for klass,config in self.config:
      plugin = None
      try:
        plugin = eval(klass)
      except (NameError, AttributeError),e:
        names = klass.split(".")
        mod = __import__(".".join(names[0:-1]))
        globals()[names[0]] = mod
        for name in names[1:-1]: mod = getattr(mod, name)
        plugin = getattr(mod, names[-1])

      p = plugin(config)
      make_closure = lambda obj,name: lambda *a, **k: getattr(obj,name)(*a, **k)

      for name, method in ((n,m) for n,m in getmembers(p) if ismethod(m)):
        if hasattr(method, "__plugin_method__"):
          setattr(self, name, make_closure(p, name))
        for hook_type in getattr(method, "__hook_types__", []):
          if hook_type not in self.hooks: self.hooks[hook_type] = []
          self.hooks[hook_type].append(getattr(p, name))

  def _run_hooks(self, dct, f=lambda x:x):
    result = {}
    for (name,value) in dct.iteritems():
      if name not in self.hooks: continue
      k,a = {}, value
      if isinstance(value[-1], types.DictType):
        k,a = value[-1],value[0:-1]
      result[name] = [m(*a, **k) for m in f(self.hooks[name])]
    return result

  def run_hooks(self, dct):
    return self._run_hooks(dct)

  def reverse_run_hooks(self, dct):
    return self._run_hooks(dct, reversed)

class Plugin(object):
  def __init__(self, config):
    self.config = config

method = lambda f: setattr(f, "__plugin_method__", True) or f
hook = lambda *args: lambda f: setattr(f, "__hook_types__", args) or f

機能的には

  • クラスに対して設定とともにメソッドを追加する(setattrします)
  • 任意のhookの登録と実行

ぐらいです。

使い方は

import pluggable
class People(pluggable.Pluggable):
  pass

class Hacker(pluggable.Plugin):
  @pluggable.method
  def hack(self):
    print self.config["msg"]

  @pluggable.hook("hello")
  def hello(self, msg1, msg2, msg3):
    print "Hello World!!", msg1, msg2, msg3

class Otaku(pluggable.Plugin):
  @pluggable.method
  def niconico(self, msg):
    print self.config["msg"], msg

  @pluggable.hook("hello")
  def hello(self, msg1, msg2, msg3):
    print u"にぱー", msg1, msg2, msg3

みたいにプラグインクラスを作っておいて

p = People()
p.load(
  ("plugins.Hacker", {"msg": "Happy Hacking!"}),
  ("plugins.Otaku",  {"msg": u"ニコ厨ですが"})
)
p.hack() #=> Happy Hacking!
p.niconico(u"なにか?") #=> ニコ厨ですが なにか?
p.run_hooks({"hello":([1,2],{"msg3":3})})
#=> Hello World!! 1 2 3
#=> にぱー 1 2 3
p.reverse_run_hooks({"hello":[1,2,3]})
#=> にぱー 1 2 3
#=> Hello World!! 1 2 3

という感じで使います。あとは設定の部分をyamlなんかに押し出せばオッケーというわけ。

モジュールのインポートは自動でやってくれます。Pythonは地味に動的なimportが面倒だったので、ライブラリにまとめたことでだいぶ手元の小物アプリがすっきりしました。  


  • 2008/01/27 若干修正を加えました。

システム開発の話ではありません。

最近、やっぱりというかなんというか初音ミクに刺激されて音楽をもう一回やりたくなったんです。

中学高校と音楽ばっかりやっていたので、大学の4年間やってないくらいで忘れてないだろう、と思ってたんです。

でも、いろいろ忘れているわけですよ、実際。


クラシック的な理論を習っていたので、感性優先で作りたいと思っても理論がわかってないと不安になっちゃう。というわけで、現在、和声法とか自由対位法とか復習しています。その次はFMとアナログでの音作りの感触を取り戻すことかな・・・

でもやっぱり基礎の基礎って絶対に忘れないもんだな、と思った。久しく使ってない知識だけど、トニイホロヘハ、ヘロホイニトハ、シミラレソドファ、ファドソレラミシ、これはしっかり覚えていた。


知識を保っていくことはほんとに難しい。人間の脳の記憶容量は普通に生きている限りだと無限に近い。storeにはあまり失敗しない。結局retrieveできなくなっていくのが問題だったりする。

それを大学の講義で習って、しばらくして自分用のメモを残すようになった。

今、昔書いた曲の譜面(ソース)をみても何でこういう進行にしたか、わからない。今回、勉強しなおしながらメモを残している。なんか、ドキュメントがないとわかんないやつになっちゃったみたいですごくむなしい。けど、やっぱりメモを残すのが一番効率がいい。記憶はそう簡単に消えない。retrieveする手がかりがあればいい。基礎とか、メモする必要はない。自分が忘れそうなことをうまく見抜いて、残していく。

これは幅広くやろうとしてしまう人の宿命だよなあ。言語にしても、実は自分用メモを見ないとまったく書けない、というレベルになってしまっているものもあるんだよねえ(HaskellとかSmalltalkとか・・・最近はどう書く?orgでコードを見るおかげでそこまでひどくないが)。


どう書く?orgと言えば主に Scalaで参戦 してるわけですが、ブログでほとんどScalaに言及してないな。まぁ、Scalaの記事とか読みたい人もあんまりいないだろうし、いいか。