Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

protocolを使って既存の関数の振る舞いを変える

こんにちわ、高校生です。
今回はprotocolを使った話です。

通常の場合

user=> (bit-and "生" "死")
IllegalArgumentException bit operation not supported for: class java.lang.String  clojure.lang.Numbers.bitOpsCast (Numbers.java:994)

bit-andはNumbersしか受け付けない関数なので当たり前のごとくうまくいきません。
異なる型でもいい感じに処理をして欲しい場合にはprotocolで既存関数も拡張ができます。

(defprotocol bit-protocol
  (bit-and [x y]))

(extend-protocol bit-protocol
  java.lang.String
  (bit-and [x y]
    (let [a (Character/codePointAt x 0)
          b (Character/codePointAt y 0)
          c (bit-and a b)]
      (apply str (Character/toChars c))))

  java.lang.Object
  (bit-and [x y]
    (clojure.core/bit-and x y)))

(bit-and "生" "死")

上記のように型ごとに処理が書けます。

Warning: protocol #'user/bit-protocol is overwriting function bit-and
WARNING: bit-and already refers to: #'clojure.core/bit-and in namespace: user, being replaced by: #'user/bit-and

Warningが出るものの既存の関数をoverwriteすることができます。
もちとんns上も置き換わるのでそちらの警告も出ますが。

Parsecを使ってjinja2風テンプレートをパースする。

http://github.com/mopemope/clarsec2を使って以前のjinja2風のテンプレを解析する。
まずはおおまかなブロックを検出するだけだけど。

input

<html>
  <body>
    {% foo %}
      {{ variable }}
    {% endfoo %}
    <div>
      {#_
      Comment
      Comment
      #}
    </div>
  </body>
</html>

コード

(use '[clarsec])
(use '[clarsec.monad])
(require '[clojure.string :as string])
(import '(java.util.regex Pattern))

(def sp-chars #"([\\\\*+\\[\\](){}\\$.?\\^|])")

(defn- esc-regex [in]
  (string/replace in sp-chars "\\\\$1"))

(defn- build-reg [args]
  (string/join "|" (map esc-regex args)))

(defn- create-regex [tags]
  (Pattern/compile (str "^(.*?)(" (build-reg tags) ")") Pattern/DOTALL))

(defmacro make-data [k]
  `(fn [x#] {~k x#}))

(defn text-data-p [regex conv-fn]
  (make-parser
    (fn p-text-data [^String strn]
      (let [found (re-find (re-matcher (create-regex regex) strn))
            [pair text token] (or found [nil nil nil])]
        (if (and token (not= token pair))
          (consumed (conv-fn text) (.substring strn (- (.length pair) (.length token))))
          (failed))))))

(defmacro surround2 [st ed]
  `(fn [p#] (between (symb ~st) (symb ~ed) p#)))

(defn combine [p last]
   (let-bind [r p
              l last]
            (result (concat [r] l))))

;; -------------------------------------

(def tag-block ((surround2 "{%" "%}")
                  (<$> (make-data :tag-block) identifier)))

(def variant-block ((surround2 "{{" "}}")
                  (<$> (make-data :variant-block) identifier)))

(def comment-block ((surround2 "{#" "#}")
                  (<$> (make-data :comment-block) (text-data-p ["#}"] #(str %)))))


(def text-data (text-data-p ["{{" "{%" "{#"] (make-data :text-data)))

(def all
  (<$> (make-data :text-data)
     (make-parser
       (fn p-any-token [strn]
         (consumed strn "")))))

(def blocks (many1 (<|> text-data tag-block variant-block comment-block)))

(def content (slurp "/tmp/index.html"))
(parse$ (combine blocks all) content)

最後の部分はまあ適当なんですけど。
上半分はutilとかキモいパーサーとかです。
正規表現とミックスしたものが使えるので便利ですね。

肝心はココです。
なんとなくどんなものの組み合わせ構成されてるかわかりますね。

(def tag-block ((surround2 "{%" "%}")
                  (<$> (make-data :tag-block) identifier)))

(def variant-block ((surround2 "{{" "}}")
                  (<$> (make-data :variant-block) identifier)))

(def comment-block ((surround2 "{#" "#}")
                  (<$> (make-data :comment-block) (text-data-p ["#}"] #(str %)))))


(def text-data (text-data-p ["{{" "{%" "{#"] (make-data :text-data)))

(def all
  (<$> (make-data :text-data)
     (make-parser
       (fn p-any-token [strn]
         (consumed strn "")))))

(def blocks (many1 (<|> text-data tag-block variant-block comment-block)))

(def content (slurp "/tmp/index.html"))
(parse$ (combine blocks all) content)

結果:

{:type :consumed, :value (({:text-data "\n \n "} {:tag-block "foo"} {:variant-block "variable"} {:tag-block "endfoo"} {:text-data "

\n "} {:comment-block "_\n Comment\n Comment\n "}) [:text-data "
\n \n\n\n"]), :rest ""}

一応パースできてるっぽい。
正規表現エスケープまみれになったコードよりわかりやすく(?)スッキリ(?)書けます。
速度はもちのろん出ません!!!
(<|>でtry-errorを繰り返しまくるので)

Parsecを使ってパースする

こんにちわ、高校生です。
にわかLisperです。みなさん、本当にClojure書いてますか?
正規表現とかしんどいし関数型言語ならParsec使えよ!という話があると思うので適当に書いてみました。

まずはcsvですよね。

(use '[clarsec])

(def input
       "Year,Make,Model,Length
       1997,Ford,Model-350,234
       2000,Mercury,\"Model 800\",238")

(def cell (lexeme (<|> string-literal
             (stringify (many (none-of ",\n"))))))

(def line (sep-by-1 cell comma))

(def csv (sep-by line eol))

(:value (parse$ csv input))

まあこんな感じです。分かる人にはまあなんとなくわかると思います。
結果:

 (("Year" "Make" "Model" "Length") ("1997" "Ford" "Model-350" "234") ("2000" "Mercury" "Model 800" "238"))

とまあパースできてますね。

次に簡易でxmlをパースしてみます。

(use '[clarsec])
(use '[clarsec.monad])


(def input
     "    <library>
              <book>
                  <title>Joy of Clojure</title>
                  <author>Fogus</author>
              </book>
              <book>
                  <title>Structured and Interpretation of Computer Programs</title>
                  <author>MIT</author>
              </book>
          </library>")

(defn arrows [p] (between (symb "<") (symb ">") p))
(def open-tag (arrows identifier))
(defn close-tag [expect-name] (arrows (symb (str "/" expect-name))))

(defn element [p]
  (let-bind [tag-name open-tag
             contents p
             (close-tag tag-name)]
            {(keyword tag-name) contents}))

(def xml
     (let [list$ #(flatten (list %&))]
       (element
        (<|> (<$> #(apply merge-with list$ %) (many1 (lazy xml)))
             (stringify (many (<|> letter space)))))))

(:value (parse$ xml input))

betweenとか便利ですよね。
えーlet-bindとかありますけど。。。モナド周りとかまあなんとなくといった感じです。
要素は辞書形式で表しています。
結果をflattenで綺麗にしています。
xmlの定義で自身を参照していますが、lazyにしておかないとStackが溢れます。
(要はdelayで必要になったときに計算を始めます)

結果はこんな感じ。

{:library {:book ({:author "Fogus", :title "Joy of Clojure"} {:author "MIT", :title "Structured and Interpretation of Computer Programs"})}}

エラーになる場合

;; 数値を許可していないので失敗する
(parse$ xml "<test>ABC123</test>")

結果

{:error-line <test>ABC1, :line-number 1, :type :failed}

とある程度どこで失敗したかわかります。

clarsecあれこれ

元々、clarsecはvimclojureの人が公開していたのですが闇に消えています。

他の開発者の人がそれらをgithubとかにあげていたのですが、エラー処理がなかったりエラー処理を実装しようとしていて放置されていたりと散々な感じでした。
仕方ないのである程度みんなのコードを寄せ集めたりして色々やって少し動くとこまでできました。
リポジトリは以下です。複雑なものはまだ試してないので自己責任で。
いろいろ手直ししてくれる人も募集しています。
https://github.com/mopemope/clarsec2

jinja2風なテンプレートをパースする

こんにちは、高校生です。
Parserはいつも人の使ったり、ragel先輩に吐かせたり楽してたのですがシンプルなものなら書けるかなと思い書いてみました。

(import '(java.util.regex Pattern))
(require '[clojure.string :as string])

(def sp-chars #"([\\\\*+\\[\\](){}\\$.?\\^|])")

(def ^:dynamic *bt* "{%")
(def ^:dynamic *bv* "{{")
(def ^:dynamic *bc* "{#")

(def ^:dynamic *et* "%}")
(def ^:dynamic *ev* "}}")
(def ^:dynamic *ec* "#}")

(defrecord Text [data])
(defrecord Tag [data])
(defrecord Variable [data])
(defrecord Comment [data])

(defn- esc [hin]
  (string/replace in sp-chars "\\\\$1"))

(defn- build-reg [args]
  (string/join "|" (map esc args)))


(defn- create-regex [tags]
  (Pattern/compile (str "^(.*?)(" (build-reg tags) ")") Pattern/DOTALL))

(def bregex (create-regex [*bt* *bv* *bc*]))
(def eregex (create-regex [*et* *ev* *ec*]))
(def reg {true bregex false eregex})

(def content (slurp "/tmp/index.html"))

(defn- get-node [token text]
  (condp = token
    *bt* (Tag. (string/trim text))
    *bv* (Variable. (string/trim text))
    *bc* (Comment. text)
    (Text. text)))

(defn- parse-tmpl [input]
  (loop [input input mode true next-token nil ast []]
    (let [found (re-find (re-matcher (reg mode) input))
          [pair text token] (or found [input input nil])
          ast (if (empty? text) ast (conj ast (get-node next-token text)))]
      ; (println "pair" pair)
      ; (println "text" text )
      ; (println "token" token)
      (if token
         (let [rest (.substring input (.length pair))
               mode (not mode)]
           (recur rest mode token ast))
        ast))))

(parse-tmpl content)

思ったより普通ですね。

テンプレート

<html>
  <body>
    {% foo %}
      {{ variable }}
    {% endfoo %}
    <div>
      {#_
      Comment
      Comment
      #}
    </div>
  </body>
</html>

結果

[#user.Text{:data "\n \n "} #user.Tag{:data "foo"} #user.Text{:data "\n "} #user.Variable{:data "variable"} #user.Text{:data "\n "} #user.Tag{:data "endfoo"} #user.Text{:data "\n

\n "} #user.Comment{:data " \n Comment \n Comment\n "} #user.Text{:data "\n
\n \n\n"}]

とまあシンプルなASTができたっぽい。
Nodeをdefrecordで作ってるので組み立てる際にprotocolを定義してなめればそれなりのテンプレートエンジンもすぐ作れそうですね。

継続的にタスクを実行するleiningen plugin

こんにちわ、高校生です。
leiningenも2.0-previewが出てきていろいろ機能強化されてきていますね。
ただleiningen 2.0ではinteractiveタスクがなくなっています。
ビルド毎にleiningenを起動をするのもだるいなあと思い、プロジェクト内のファイル変更を監視し
タスクを起動するpluginを書きました。
もちろんleiningen 2.0じゃないと動きません。

(ns leiningen.cont
  "Continuous target task"
  (:use [clojure.string :only (split)]
        [clojure.repl :only (pst)])
  (:require [leiningen.help :as help]
            [watchdog :as watchdog]))

(defn- create-taskmap [task x]
  (let [func-name (second (split (str task) #"\."))]
    (assoc x func-name (str task))))

(defmacro import-task []
  (loop [task help/tasks x {}]
    (if (nil? task)
      `(def ^:dynamic *task* ~x)
      (do
        (require (first task))
        (recur (next task) (create-taskmap (first task) x))))))

(import-task)

(defn- get-lein-tasks [tasks]
  (loop [args tasks result nil]
    (if (nil? args)
      (reverse result)
      (recur (next args)
             (conj result [(get *task* (first args)) (first args)])))))

(defn- call-task [[nm k] project]
  (let [nm-map (ns-interns (symbol nm))
        fun (get nm-map (symbol k))]
    (fn [x]
      (apply fun [project]))))

(defn- create-func [tasks project]
  (fn [x]
    (try
      (println "Change Files" x)
      (loop [nms tasks]
        (if (nil? nms)
          nil
          (do
            ((call-task (first nms) project) x)
            (recur (next nms)))))
      (catch Exception _ ))))

(defn cont [project & args]
  (let [paths (concat [(str (:root project) "/project.clj")]
                    (:source-paths project)
                    (:resource-paths project)
                    (:test-pathes project))
        tasks (get-lein-tasks args)
        task-func (create-func tasks project)]
    (watchdog/set-interval (* 1000))
    (binding [leiningen.test/*exit-after-tests* false]
      (watchdog/watch-start paths task-func))))

恐ろしくベタですね。
監視してるファイルはproject.cljも含み、source, resource, testです。
既にClojarsにあげているのでlein-contで検索すれば出てくると思います。
使い方としてはpluginをインストールし、

lein cont

でファイル監視を行い継続的にタスクを実行します。

autotest風にするならば

lein cont test

定期的に文法などのチェックを行うなら

lein cont check

複数のタスクを実行したりすることもできます。

lein cont check test

ソースなどはgithubにあがってるので何かあればそちらへ。

Python3 Advent Calendar - Pythonで2/3両方で動くコードを書く(C/API)

これは Python3 Advent Calendarの記事です。
こんにちわ、高校生です。
先日、子供(遼くん)が無事生まれましたが、毎日お世話で忙しいです。

Python3 Advent Calendar ということでPython3に関して書いてみたいと思います。
Python3対応というのはまあいろいろあるんですが、Pure Pythonで両方動くコード書こうとなると文法とかいろいろ面倒ですね。
ですがC/APIで書くとマクロで大きく処理を2系、3系と切り替えができるのでので容易に両バージョンをサポートするコードが書けます。
ということで2/3両方をサポートするコードをC/APIを中心に幾つか書いてみたいと思います。
(3系といっても3.2以降と思ってください)

Python3の判別

Python3であるかどうかは元々用意されているPythonバージョンを確認できるマクロで容易に判断できます。

例:Python3系

#if PY_MAJOR_VERSION >= 3
#define PY3
#endif

単純に3系の場合にはこんな感じですね。

PythonのC/APIをCで利用する際のAPIが3.1以降変わったため、それらを考慮するならばこのような書き方になります。

例:Python3.1以降

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) || PY_MAJOR_VERSION > 3
#define PY3
#endif

PY_MINOR_VERSIONの他にも、HEXバージョンで判断する方法もあります。
(多分HEXが一番楽かも)
毎回、長いマクロ名を書くのがだるいので共通ヘッダなどに短めに再定義してしまうと楽だと思います。

モジュールの定義

モジュールの作成方法も2/3で変わっています。
ここでは先程の判定マクロを使用して2と3で分岐しています。

例:書き方の例

static PyMethodDef CoreMethods[] = {
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


#ifdef PY3
#define INITERROR return NULL

static struct PyModuleDef core_module_def = {
    PyModuleDef_HEAD_INIT,
    MODULE_NAME,
    NULL,
    -1,
    CoreMethods,
};

PyObject *
PyInit_core(void)
#else
#define INITERROR return

PyMODINIT_FUNC
initcore(void)
#endif
{
    PyObject *m;

#ifdef PY3
    m = PyModule_Create(&core_module_def);
#else
    m = Py_InitModule3(MODULE_NAME, CoreMethods, "");
#endif
    if(m == NULL){
        INITERROR;
    }
...
#ifdef PY3
    return m;
#endif

}

大きく異なるのはPy_InitModule3などがなくなった点です。
モジュール定義用の構造体にモジュール情報をセットしてPyModule_Createでモジュールを作成します。
またPython3以降は作成したモジュールオブジェクト返さないといけません。
(返り値が異なる。初期化メソッド名なども変更)
その他、モジュールの関数の定義などは特に変更はないです。

PyInt系関数の廃止

Python3からはPyInt_から始まる関数は排除されています。
Python3からは全てPyLongに統一する必要があります。
特に問題なければ2/3でもPyLongの関数を使ってしまってもいいかも知れませんね。
生成だけではばくチェックなどもPyLongの関数に変更になります。

#ifdef PY3
    if(!PyLong_Check(sec)){
#else
    if(!PyInt_Check(sec)){
#endif

文字列の生成

色々なところで触れられていますが、Python3では通常文字列は全てUnicode型になります。
今までのstr型と同じような動作にしたい場合にはPyBytes系(PyString)の関数を使用することになります。

    char *tmp;

...

#ifdef PY3
    return PyUnicode_FromString(tmp);
#else
    //あるいはreturn PyString_FromString(tmp);
    return PyBytes_FromString(tmp);
#endif

Python2.6以降はPyBytes系の関数が既に存在しているため、そちらを使う事を推奨します。
PyUnicode_FromStringは渡された文字列がUTF-8 encodedなものとして処理をします。多くの場合はこれで事足りると思います。

BytesとUnicode

Python3でも全てUnicodeを使えばいいのか?というとそうではありません。
生データを必要とする場合には、適時PyBytes_FromStringでBytesを返す必要があります。

例:

  1. ファイルからのデータの読込結果
  2. ソケットから読み込んだデータ

興味があればPEP-3333などを読むと使い分けに対する理解が深まるかも知れません。

char*型の参照

逆にPythonの文字列型をCで参照する場合にもUnicodeAPIを使います。

    char *ch;
    char *tmp;
....

#ifdef PY3
    PyObject *latin_item;
    latin_item = PyUnicode_AsLatin1String(ip);
    ch = PyBytes_AsString(latin_item);
    Py_DECREF(latin_item);
#else
    ch = PyBytes_AsString(ip);

#endif

上記の例はlatin1のUnicodeである前提で処理しています。
(ネットワーク系の処理ではこれで十分だったりします。)
必要に応じてPyUnicode_Decode関数でデコードし、参照する必要があります。
上記ではUnicodeかどうかのチェックをしていませんが、必要に応じてPyUnicode_Checkを呼び出して下さい。

C API の提供、およびImport

あまり使われてないようですがC/APIで書かれたモジュールは他のCモジュールでも使用できるAPIを提供することができます。
2.7および3.2以降からは以前のPyCObject_FromVoidPtrではなくPyCapsule_系のAPIを使用します。

例:提供する側

#if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) 
#define USE_PYCAPSULE
#endif

...

        PyObject *c_api_object;
        static void *_C_API[8];
        ....

#ifdef USE_PYCAPSULE
        c_api_object = PyCapsule_New((void *) _C_API, "xxxxx._C_API", NULL);
#else
        c_api_object = PyCObject_FromVoidPtr((void *) _C_API, NULL);
#endif
        if (c_api_object != NULL)
        {
                PyModule_AddObject(m, "_C_API", c_api_object);
        }

例:使用する側

#ifdef USE_PYCAPSULE
#define C_API_Import() \
{ \
        _C_API = (void**)PyCapsule_Import("xxxxx._C_API", 0); \
}
#else
#define C_API_Import() \
{ \
        PyObject *module = PyImport_ImportModule("xxxxx"); \
        if (module != NULL) { \
                PyObject *c_api_object = PyObject_GetAttrString( \
                        module, "_C_API"); \
                if (c_api_object != NULL && PyCObject_Check(c_api_object)) { \
                        _C_API = \
                                (void **) PyCObject_AsVoidPtr(c_api_object); \
                        Py_DECREF(c_api_object); \
                } \
                Py_DECREF(module); \
        } \
}
#endif

Pure PythonでPython3を判別する

Python側でも簡単に判別することができます。
マクロ同様、短めの関数名で定義しておくと使うとき楽だと思います。

import sys

def is_py3():
    return sys.hexversion >=  0x3000000

モジュール定義時に大きく分岐させることで両方で動くモジュールを作成することが出来ます。

例:

if is_py3():

    #python3系の処理を書く

    class socket(_socket.socket):
	....
else:

    class socket(_socket.socket):
	....

とざっくり両バージョンで動かすためのコードを紹介しました。
両方をサポートするコードを書いた場合にはtoxなどでテストをすることをオススメします。
(toxについてはそのうち誰かが書くと思います)

次はid:doloopwhileさんです。