$importでpythonのmoduleをimport出来るようにしてみた

github.com

今回はimport出来るようにしてみた。例えば以下の様にして使うことが出来る。そう言えば、前回の記事では使い方を説明していなかった。使い方は単純で zenmai.compile(d, module) という感じで呼び出すだけ。注意点として順序は保持してもらう必要がある(dictknifeというのはyamlのloadに便利なutilityというだけなのでなくてもどうにかなる)。

from zenmai.actions import import_, from_  # これ


if __name__ == "__main__":
    import zenmai
    import sys
    from dictknife import loading

    loading.setup()  # xxx
    d = loading.loadfile(None)
    d = zenmai.compile(d, sys.modules[__name__])  # ここに渡したmoduleが触れる
    loading.dumpfile(d)

このようなコードを書いてあげると以下のようなyamlに対して

code:
  $import: math
nums:
  ceil:
    $math.ceil: 1.5
  floor:
    $math.floor: 1.5

以下のような出力を返すようになる。

nums:
  ceil: 2
  floor: 1

微妙な点

ただ微妙な点が残っている。それは戻り値が出力として不要な階層の部分に不格好な記述が必要になる点。例えば、上の例で言うと、importを行う部分のところに code と書いているが。これは実は何でも良い。呼ばれるactionで戻り値が何も返さなかった場合にはその階層自体がまるっと消えて無くなる。内部的には missing = object() みたいなものを利用してあれこれしている。すごく汚い。

fromにも対応

実は import の他に from も対応している。例えばこういう感じも書ける。

code:
  $from: math
  import:
    - ceil
    - floor
nums:
  ceil:
    $ceil: 1.5
  floor:
    $floor: 1.5

予約語

ちなみに、fromimport予約語なので、pythonのコード上でこのような予約語のメソッドなどを定義できないかと思いきや実は普通にできる。ただしsyntax errorになるのでそれを迂回する必要はある。こういう感じ。

A = type("A", (), {"import": lambda self: "import!!"})
print(getattr(A(), "import")())  # import!!
# print(A().import) syntax error

なんとなくの方針として、今回は、こういう汚い感じのコードは書かなくてすむようにしよう、ただし予約語のものもそのまま書けるようにしようという感じにすることにしたので。実装では keyword.iskeyword を使って分岐している。fromが自動的にfrom_を探して呼び出すということにした。予約語を見つけてくれるkeyword packageはたまに便利だったりする。

yaml上で動くtoy言語を作り始めた

yaml上で動くtoy言語を作り始めた。 これ。

github.com

今回はコードを書く以外になるべく文章によるアウトプットを増やしてみようという試みも裏で行うことにした(そんなわけでこの記事を書いている)。

yaml上で動く言語

現在のところはlispのマクロのようなものをイメージしている。コードを読み込み読み込んだコード自体が変換され出力されるというようなもの。上手くいくかはわからない。

どこまで真面目にやるかというのもけっこう悩みどころではあるけれどとりあえず行き当たりばったりで進めてみることにする。

すごく簡単に書くと以下のような記述になる。$で始まるものが関数(action)として実行される(個人的には $ のprefixは好きではないのだけれど。yamlの構文との兼ね合いで今のところこれが一番無難だろうという考えになっている)。

$<action>:
  <body>
<kwargs>

上のようなyaml<action>(<body>, **<kwargs>) という形の関数呼び出しに変換されるようなイメージ。 例えば、以下のようなyamlは、suffix({"name": "foo"}, suffix="+") という形に変換される。

$suffix:
  name: foo
suffix: +

actionが存在した階層の同一階層のものはキーワード引数として扱われる。また、actionの戻り値がそのまま出力になる。

ネストした呼び出し

ネストしたactionの呼び出しは以下の様になる。

definitions:
  $suffix:
    $suffix:
      name: foo
    suffix: +
  suffix: "-"

関数の呼び出しは内から外に行われる。上の例では suffix(suffix({"name": "foo"}, suffix="+"), suffix="-") の意味。従って以下のような結果になる。

name+-: foo

ネストした呼び出しがまだ読みづらい気がする。そしてactionとその引数の間が離れていくのがわりとつらい。

readmeを書く

今回はそれなりに早い段階でreadmeを書くことにした。毎回readmeはテキトウになってしまうのでreadmeを充実させるというのも今回の目標の1つにしてみることにした。とりあえず、実行例を載せようということで、トップレベルで make readme を呼ぶとreadmeが生成されるというようにした。markdownに比べてReSTはコードブロックにインデントが必要になるのが面倒に感じる。