自分はpythonを使ってると、データファイルを作るときはついついYAMLファイル形式を選んでしまいます。
例えば レイトレーシング 2018春 の 物体データをデータファイルに分離 てな具合に。
pythonからだと扱いが簡単で、インデントが重要なところとか、いかにもpythonと相性が良さそうですね。
そしてpythonの遅さに業を煮やして、C言語に書き換えたりしたりしますよね。
そこでC言語のYAMLのライブラリは...あれ?
C++のは充実してるのに、C言語は...あれ?
よく考えると、そらそうですよね。
YAMLで扱うリストと辞書。 各要素の型は同じである必要なくバラバラでOKです。
C言語で読み込んだとして、どうやって保持すれば良いでしょうか?
まずは、リストや辞書のC言語のライブラリ。 標準ライブラリ的なものには、おそらく無くて、 標準じゃない一長一短なライブラリは、それこそ星の数ほどある事でしょう。
うーむ。まずはリストや辞書。そこを何とかした上でのYAMLのパーサ。 どうせなら、自分で作ってみるべしですかね。
と言っても「YAML形式を完全に扱えるように」などと目論むと、挫折するのが目に見えているので、 まずは、ちょー簡単なところだけで試していきます。
pythonには当然PyYAMLモジュールが存在するので、pythonで書く必要は全くありません。
重々承知です。
いきなりC言語で書き出すよりも、 まずpythonで肩慣らししてからC言語にもっていくとしましょう。
まずは一発目。ちょー簡単仕様。「えいや」で書いてみました。
ezyaml.py簡単なYAMLファイルを作って試しておきます。
$ cat ck1.yaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ $ chmod +x ezyaml.py $ cat ck1.yaml | ./ezyaml.py load obj {'foo': 1, 'bar': '2', 'hoge': ['hello', 999, 12.3], 1: 'fuga', 2: 'guha', 3: {'path': '/usr/bin/env', 'usr': {'bin': {'env': ['python']}}, 'names': ['zaku', 'gufu', 'dom']}} dump foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $
まずはOK
少し大きなYAMLファイルも試してみたいところ。
そういえば、お仕事で使ってる computing.yaml。 (加藤先生、お世話になっております)
$ wget https://raw.githubusercontent.com/CPFL/Autoware/master/ros/src/util/packages/runtime_manager/scripts/computing.yaml $ wc -l computing.yaml 4212 computing.yaml
4200行。でかい。
最初作ったときは、本当小さかったのですが...
私の記憶が正しければ、当初「XMLにしようか」「いやjsonでは?」といった段階で、 私がYAMLでデータを作って試していると、「ではYAMLでいきましょうか」となり、そのまま現在に至る...なはず。
$ python Python 3.6.5 (default, Mar 30 2018, 06:41:49) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> >>> f = open('computing.yaml', 'r') >>> s = f.read() >>> import yaml >>> o = yaml.load(s) >>>
まずは、PyYAMLで読み込み。
ezyamlでdumpしてみます。
>>> >>> import ezyaml >>> t = ezyaml.dump(o) >>> print(t) name: Computing subs: - subs: - name: Localization desc: Localization desc sample subs: - name: gnss_localizer desc: gnss_localizer desc sample subs: - name: fix2tfpose desc: fix2tfpose desc sample cmd: roslaunch gnss_localizer fix2tfpose.launch param: gnss gui: stat_topic: - gnss - name: nmea2tfpose desc: nmea2tfpose desc sample cmd: roslaunch gnss_localizer nmea2tfpose.launch param: gnss gui: stat_topic: - gnss - name: lidar_localizer desc: lidar_localizer desc sample subs: - name: ndt_mapping desc: ndt_mapping desc sample cmd: roslaunch lidar_localizer ndt_mapping.launch param: ndt_mapping : - name: use_gpu desc: use_gpu desc sample label: use_gpu kind: checkbox v: False cmd_param: dash: delim: := >>>
なにやら、それらしい表示。
この出力を、ezyamlでロードしなおせるか!?
>>> >>> o2 = ezyaml.load(t) Traceback (most recent call last): File "", line 1, in File "/Users/kondoh/ezyaml/ezyaml.py", line 107, in load obj = get(idt, kind, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 94, in get return get_dict(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 34, in get_dict_v return get_list(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 65, in get_list v = get(idt_, kind_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 94, in get return get_dict(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 34, in get_dict_v return get_list(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 65, in get_list v = get(idt_, kind_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 94, in get return get_dict(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 34, in get_dict_v return get_list(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 71, in get_list lst += get_list(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 65, in get_list v = get(idt_, kind_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 94, in get return get_dict(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 34, in get_dict_v return get_list(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 65, in get_list v = get(idt_, kind_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 94, in get return get_dict(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 29, in get_dict_v return get_dict(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 53, in get_dict v = get_dict_v(idt, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 29, in get_dict_v return get_dict(idt_, lines) File "/Users/kondoh/ezyaml/ezyaml.py", line 59, in get_dict dic.update( get_dict(idt, lines) ) File "/Users/kondoh/ezyaml/ezyaml.py", line 46, in get_dict (k, v) = s.split(':') ValueError: too many values to unpack (expected 2) >>>
残念。ダメでした。
辞書のキーか値のどちらかが文字列で、':' を含んでいたため、
(k, v) = s.split(':')
ここで右辺のタプルの要素が3以上になってしまいエラーですね。
computing.yaml を試すのは、まだまだ時期尚早でした。
ezyamlでの出力を'ez.yaml'として保存。 1行に':'が2つあるパターンを抽出してみると...
>>> >>> f = open('ez.yaml', 'w') >>> f.write(t) 97544 >>> f.close() >>> ^D $ $ ls -l ez.yaml -rw-r--r-- 1 kondoh staff 97544 8 29 13:41 ez.yaml $ $ grep '.*:.*:.*' ez.yaml | sort | uniq depend_bool: lambda v : v == 0 depend_bool: lambda v : v == 1 depend_bool: lambda v : v == 1 cmd: roslaunch viewers viewers.launch viewer_type:=image_d_viewer cmd: roslaunch viewers viewers.launch viewer_type:=image_viewer cmd: roslaunch viewers viewers.launch viewer_type:=points_image_d_viewer cmd: roslaunch viewers viewers.launch viewer_type:=points_image_viewer cmd: roslaunch viewers viewers.launch viewer_type:=traffic_light_viewer cmd: roslaunch viewers viewers.launch viewer_type:=vscan_image_d_viewer cmd: roslaunch viewers viewers.launch viewer_type:=vscan_image_viewer delim: := desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas. label: Plane number: label: accel_rate(m/s^2) : label: angle_error(degree) : label: budget(ms): label: lateral_accel_limit : label: lowpass_gain_angular_z : label: lowpass_gain_linear_x : label: nice: label: period(ms): label: pitch: label: position_error(m) : label: prio: label: roll: label: x: label: y: label: yaw: label: z: $
けっこうありますね。
しばらくpython版をブラッシュアップしてみます。
ブロックスタイルの辞書の形式 「キー: 値」を
(k, v) = s.split(':')
で分けようとしたのが、あさはかでした。もうちょっと真面目にパースしてみます。
例えばキーや値の文字列中に':'を含めたいときは、
'abc:def': "10:12"
などとクオートで囲って明示的に文字列と主張するだろうし...
右側の「値」の方は、以降には明白にデータ文字列なので
'abc:def': ab:cd:ef:01:23:45
でも大丈夫なはずです。
またクオートは、文字列と主張するために、先頭と末尾につける場合と、 単にクオートの文字として、文字列に含めたい場合があります。
msg: can't find
クオートがキー側の文字列に入ってる事もあるでしょう。
can't find: don't move
そして、辞書じゃなくて単なる文字列のデータ
now '99:100' score
むむむ。
can't find: don't move now '99:100' score
この2つは機械から見ると、ほぼ同じ構成。
[ can ][ 't find: don' ][ t move ] [ now ][ '99:100' ][ socre ]
さて...
処理を単純にするため制限を設けます。
文字列中に単独でクオートを使うときは、 全体を別の種類のクオートで囲って、文字列である事を主張してもらう事にします。
"can't find": "don't move" ならば [ "can't find" ][ : ][ "don't move" ]
can't find: don't move ならば [ can ][ 't find: don' ][ t move ]
これで、クォートなしの文字列中で最初に登場する':'を、 辞書のキーと値を区切るコロンと解釈する事にしてみます。
メインの処理をちょっと変えました。
$ cat v2.patch | patch -p1 $ cat ck1.yaml | ./ezyaml.py 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom bar: 2 hoge: - hello - 999 - 12.3 foo: 1 {1: 'fuga', 2: 'guha', 3: {'path': '/usr/bin/env', 'usr': {'bin': {'env': ['python']}}, 'names': ['zaku', 'gufu', 'dom']}, 'bar': '2', 'hoge': ['hello', 999, 12.3], 'foo': 1} $
標準エラー出力を隠したければLinuxだと " 2> /dev/null " 追加で
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom bar: 2 hoge: - hello - 999 - 12.3 foo: 1 $ $ cat ck1.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,19 > bar: 2 > hoge: > - hello > - 999 > - 12.3 > foo: 1 >
v1のときと違う環境で試してるので、辞書の出力順が違ってますね。
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null > ck2.yaml
などと一旦ck2.yamlに出力してみてck2.yamlをロードしなおしてみると
$ cat ck2.yaml | ./ezyaml.py 2> /dev/null | diff ck2.yaml - $
一致です。
さて、computing.yaml。
$ cat computing.yaml | ./ezyaml.py err indent dict " topic_pose_stamped:"
直接のロードは、まだまだダメっすね。ちなみにエラー箇所は?
$ grep 'topic_pose_stamped:' computing.yaml topic_pose_stamped: $ grep -A2 -B2 'topic_pose_stamped:' computing.yaml param: vel_pose_connect gui : topic_pose_stamped: depend : sim_mode depend_bool : 'lambda v : v == 0'
'gui :' の次の行の字下げが、2じゃなくて3に!?
PyYAMLだと、問題なくロードに成功してます。 まぁ「字下げしてるかどうか」さえ判ればよくて、字下げの大きさは本質的な問題じゃない気もします。
でも、ブロックごとにあんまりバラバラな字下げをされてると、 戻るときに、どこに戻るべきか、ややこしそうですが...
computing.yamlの直接のロードはPyYAMLにおまかせして、 ブロックスタイルで'blk.yaml'として保存しなおしておきます。
$ python Python 2.7.6 (default, Oct 26 2016, 20:32:47) [GCC 4.8.4] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> >>> f = open('computing.yaml', 'r') >>> s = f.read() >>> f.close() >>> import yaml >>> o = yaml.load(s) >>> >>> f = open('blk.yaml', 'w') >>> f.write( yaml.dump(o, default_flow_style=False) ) >>> f.close() >>>
ついでに ezyaml でも出力して'ez.yaml'に
>>> import ezyaml >>> f = open('ez.yaml', 'w') >>> f.write( ezyaml.dump(o) ) >>> f.close() >>> >>> ^D $ head blk.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: $ head ez.yaml buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample pedestrian_fusion: param: pedestrian_fusion car_dpm: param: car_dpm
辞書の順不同問題があるので、単純にdiffはとれませんね。
では、blk.yaml を ezyaml.py でロード。
$ cat blk.yaml | ./ezyaml.py :
なんだかエラーは出てない様子!?
$ cat blk.yaml | ./ezyaml.py 2> /dev/null > blk_ez.yaml $ head blk_ez.yaml - buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm pedestrian_kf: param: pedestrian_kf
ありゃー。blk.yamlをリストと解釈? というか..
ezyaml.py def load(s): lines = s.strip().split('\n') lines = list( map(cut_tail_spc, lines) ) objs = [] while lines: s = lines[0] (idt, kind) = get_idt_kind(s) obj = get(idt, kind, lines) objs.append(obj) return objs[0] if len(objs) == 1 else objs
ここで最初、辞書と思ってロードして辞書をobjs に追加。 でも、まだテキストの行があまってて、 以降も解釈してobjsに追加を続けて、結果リストになっている...
$ grep '^- ' blk_ez.yaml | head - buttons: - is publishing - kind: checkbox - - desc: interval desc sample - - name: waypoint_clicker - curve - max: 100.0 - - name: radius_min - - name: waypoint_velocity_visualizer - areas. $ grep '^- ' blk_ez.yaml | wc -l 46
結果、要素数46のリストができてる。
怪しい箇所は 'is publishing' の前。
$ grep -B10 'is publishing' blk.yaml desc: save_filename desc sample kind: path label: Save File name: save_filename path_type: save v: /tmp/saved_waypoints.csv - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing
あー。長い文字列が、次の行に畳まれてる。
$ grep -B10 'is publishing' computing.yaml desc : save_filename desc sample label : Save File kind : path path_type : save v : '/tmp/saved_waypoints.csv' cmd_param : var_name : save_finename dash : '' delim : ':=' - name : save_velocity descs : When you save the velocity in each waypoints, Please check if /current_velocity is publishing
オリジナルのcomputing.yamlだと、このとおり。
そうか...。 辞書の値が文字列で一旦行が終る。 次の行でさらに字下げが深くなっているパターンは、通常ありえないので、 値の文字列の続きが、きてると解釈するわけか。
ちょっと検索してみると 「折り返しスタイルでは改行がスペースに置き換えられてインデントは無視」 なーるほど。
descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing
この場合、次の行が字下げされてないと折り返してる事が判りにくい。 よって次の行は字下げ+2した箇所から、続きの文字列が始まる...という事だろう。 きっと。ふむふむ。
ちなみに ezyaml で出力してみた ez.yaml を、もう一度 ezyaml でロードを試してみると
$ cat ez.yaml | ./ezyaml.py 2> /dev/null | diff ez.yaml - | head 1,75c1,73 < buttons: < car_kf: < param: car_kf < synchronization: < run: roslaunch runtime_manager synchronization.launch < desc: synchronization desc sample < pedestrian_fusion: < param: pedestrian_fusion < car_dpm:
ロードしてダンプしなおすと、元のと違いがでてます。
$ cat ez.yaml | ./ezyaml.py 2> /dev/null > ez2.yaml $ head ez2.yaml - buttons: pedestrian_kf: param: pedestrian_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm car_kf: param: car_kf
あー。これもリストになってしまってます。
$ grep '^- ' ez2.yaml - buttons: - - item_n: get_cpu_count() - - kind: num - - name: dpm_ttic - name: Computing
結果、要素6つのリストができてて、'item_n: get_cpu_count()' あたりが怪しく。
ez.yamlの対応する箇所は
$ grep -B10 'item_n: get_cpu_count' ez.yaml - - all - expand - 8 params: - name: sys vars: - kind: checkboxes name: cpu_chks v: item_n: get_cpu_count()
なーるほど。 空行があって、ezyamlでは根本まで戻ってしまい、そこで辞書は終ってると解釈。
なんでezyaml.pyは空行を出力してる? computing.yamlにさかのぼって、対応する箇所は
$ grep -B10 -A10 'get_cpu_count' computing.yaml flags : [ top, left ] depend : real_time params : - name : sys vars : - name : cpu_chks desc : cpu_chks desc sample label : CPU kind : checkboxes item_n : get_cpu_count() v : [] - name : nice desc : nice desc sample label : 'nice:' kind : num v : 0 - name : real_time desc : real_time desc sample label : Real-Time kind : checkbox
v が [] 空リスト。 辞書の値として、空リストのオブジェクトを ez.yaml で出力しようとすると
ezyaml.py def dump_list(lst, idt): lines = [] for obj in lst: s = dump_obj(obj, idt+2) s = s[:idt] + '- ' + s[idt+2:] lines.append(s) return '\n'.join(lines)
なので
>>> '\n'.join([]) ''
辞書の値が空リストで、その空リストを次の行以降に出力しようとして、 空リストの出力が空文字列で、空行になる。なるほど。
ブロックスタイルでの、空のリストと空の辞書は[]と{}だった。 これも対応したいところ。
そして空行はコメント行と見なすべきか...
はっ。そもそもロードで '#' のコメントの処理、何もしてなかった orz
TODO
前回のV2、見逃しがありました。
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,19 > bar: 2 > hoge: > - hello > - 999 > - 12.3 > foo: 1 >
「bar: '2'」が「bar: 2」に。
そして、最後に空行が追加。
修正しておきます。
$ cat v3.patch | patch -p1 $ cat ck1.yaml | python2 ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,18 > bar: '2' > hoge: > - hello > - 999 > - 12.3 > foo: 1 $ $ cat ck1.yaml | python3 ezyaml.py 2> /dev/null | diff ck1.yaml - $
なんと python2 と python3 で、dict.items() の順が変わるのですね。 知らなかった。
python3 だと生成順が守られて、python2 だとキーでソート? でも無いような...
まぁ順番は保証されないのがpythonの辞書なので、何かを期待してはいかんのでしょう。
「空リスト、空辞書の [], {} 対応」してみました。
YAMLのブロックスタイルの前提なので、 リストでも辞書でも「空」の場合はkindをother扱いで、コーディングしてみました。
あと、空文字列のときの対応も少々。
$ cat v4.patch | patch -p1 $ cat ck1.yaml | python2 ezyaml.py 2> /dev/null > ck2.yaml $ cat ck2.yaml | python2 ezyaml.py 2> /dev/null | diff ck2.yaml - $
まずはck1でOK。
$ cat ck3.yaml foo: '' bar: '2' hoge: - [] - {} - '' 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' $ $ cat ck3.yaml | ./ezyaml.py foo: '' bar: '2' hoge: - [] - {} - '' 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' {'foo': '', 'bar': '2', 'hoge': [[], {}, ''], 1: [], 2: {}, 3: {'path': '/usr/bin/env', 'usr': {'bin': {'env': []}}, 'names': [{}, [], '']}} $ $ cat ck3.yaml | ./ezyaml.py 2> /dev/null | diff ck3.yaml - $
空のリスト、辞書、文字列も大丈夫そうですね。
TODO
文字列の折り返しに対応してみました。
処理を簡単にするために、続く文字列は必ず、字下げ+2からに固定です。
クオートで囲ってる文字列が続く場合とか、あまり深く考えず適当です。
$ cat v5.patch | patch -p1 $ cat ck1.yaml | ./ezyaml.py 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom bar: '2' hoge: - hello - 999 - 12.3 foo: 1 {1: 'fuga', 2: 'guha', 3: {'path': '/usr/bin/env', 'usr': {'bin': {'env': ['python']}}, 'names': ['zaku', 'gufu', 'dom']}, 'bar': '2', 'hoge': ['hello', 999, 12.3], 'foo': 1} $ $ cat ck1.yaml | ./ezyaml.py 2> /dev/null > ck2.yaml $ cat ck2.yaml | ./ezyaml.py 2> /dev/null | diff ck2.yaml - $ $ cat ck3.yaml | ./ezyaml.py 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' bar: '2' hoge: - [] - {} - '' foo: '' {1: [], 2: {}, 3: {'path': '/usr/bin/env', 'usr': {'bin': {'env': []}}, 'names': [{}, [], '']}, 'bar': '2', 'hoge': [[], {}, ''], 'foo': ''} $ $ cat ck3.yaml | ./ezyaml.py 2> /dev/null > ck4.yaml $ cat ck4.yaml | ./ezyaml.py 2> /dev/null | diff ck4.yaml - $
まずはOK。
毎度インタプリタで起動して、ブロックスタイルで保存するのも何なので...
$ cat blk.py #!/usr/bin/env python import sys import yaml s = sys.stdin.read() o = yaml.load(s) s = yaml.dump(o, default_flow_style=False) sys.stdout.write(s) # EOF $ $ chmod +x blk.py
PyYAMLで、標準入力からYAMLをロードしてブロック形式で標準出力にダンプ。
$ cat computing.yaml | ./blk.py > blk.yaml $ $ cat blk.yaml | ./ezyaml.py :
特にエラーはなく
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | head buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm pedestrian_kf: param: pedestrian_kf $
よし。リストにならずに、正しく辞書と解釈された模様。
$ cat blk.yaml | ./ezyaml.py 2> /dev/null > ez.yaml $ wc -l blk.yaml ez.yaml 4275 blk.yaml 4262 ez.yaml 8537 total
行数は「いい線」いってる様子。
$ head blk.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: $ head ez.yaml buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm pedestrian_kf: param: pedestrian_kf
まぁ冒頭から辞書の並び順が違うので、単純な比較はできません。
$ cat ez.yaml | ./ezyaml.py 2> /dev/null | head buttons: pedestrian_kf: param: pedestrian_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm car_kf: param: car_kf $ head ez.yaml buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm pedestrian_kf: param: pedestrian_kf
ezyaml.py の出力 ez.yaml を、もう一度 ezyaml.py に入れてみても、 このように辞書の並び順が変わってます。
pythonの辞書なので、順を期待してはダメですね。
$ python Python 2.7.6 (default, Oct 26 2016, 20:32:47) [GCC 4.8.4] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import yaml >>> lst = [ 'computing.yaml', 'blk.yaml', 'ez.yaml' ] >>> objs = list( map( lambda fn: yaml.load( open(fn, 'r').read() ), lst ) ) >>> objs[0] == objs[1] True >>> objs[0] == objs[2] False
ガーン。道のりは遠い。
とりあえず、正しいオブジェクトをezyamlで出力してみます。
>>> import ezyaml >>> f = open('ezo.yaml', 'w') >>> f.write( ezyaml.dump(objs[0]) ) >>> f.close() >>> ^D $ head ezo.yaml buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample pedestrian_fusion: param: pedestrian_fusion car_dpm: param: car_dpm $ head ez.yaml buttons: car_kf: param: car_kf synchronization: run: roslaunch runtime_manager synchronization.launch desc: synchronization desc sample car_dpm: param: car_dpm pedestrian_kf: param: pedestrian_kf
うーむ。それでも並び順が変わってるから diff の意味ないか...
$ wc -l blk.yaml ez.yaml ezo.yaml 4275 blk.yaml 4262 ez.yaml 4262 ezo.yaml 12799 total
行数は一致。はがゆいですな。
TODO
辞書の順不同問題で、結果の確認があまりに「はがゆい」のでツールを作りました。
変換をかければ、辞書は消えてリストになります。 元に戻すにはもう一度変換をかければ良く、先頭要素が特別なリストだけが辞書に戻ります。
$ cat diclst.py #!/usr/bin/env python import sys import yaml magic = [ '### --- diclst --- ###' ] def conv(o): typ = type(o) if typ == dict: lst = sorted( map( lambda kv: [ kv[0], conv(kv[1]) ], o.items() ), key=lambda kv: kv[0] ) return magic + lst if typ == list: if o[:1] == magic: return dict( map( lambda kv: ( kv[0], conv(kv[1]) ), o[1:] ) ) else: return list( map(conv, o) ) return o if __name__ == "__main__": s = sys.stdin.read() o = yaml.load(s) o = conv(o) s = yaml.dump(o, default_flow_style=False) sys.stdout.write(s) # EOF $ $ chmod +x diclst.py $ cat ck1.yaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck1.yaml | ./diclst.py - '### --- diclst --- ###' - - 1 - fuga - - 2 - guha - - 3 - - '### --- diclst --- ###' - - names - - zaku - gufu - dom - - path - /usr/bin/env - - usr - - '### --- diclst --- ###' - - bin - - '### --- diclst --- ###' - - env - - python - - bar - '2' - - foo - 1 - - hoge - - hello - 999 - 12.3 $ cat ck1.yaml | ./diclst.py | ./diclst.py 1: fuga 2: guha 3: names: - zaku - gufu - dom path: /usr/bin/env usr: bin: env: - python bar: '2' foo: 1 hoge: - hello - 999 - 12.3 $ cat ck1.yaml | ./diclst.py | ./diclst.py > tmp.yaml $ diff ck1.yaml tmp.yaml 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 9a4,7 > names: > - zaku > - gufu > - dom 15,18c13,18 < names: < - zaku < - gufu < - dom --- > bar: '2' > foo: 1 > hoge: > - hello > - 999 > - 12.3
このように、辞書だと並び順が変わりますが、 リストにしてキーでソートしておけば...
$ cat ck1.yaml | ./diclst.py > tmp2.yaml $ cat tmp.yaml | ./diclst.py - '### --- diclst --- ###' - - 1 - fuga - - 2 - guha - - 3 - - '### --- diclst --- ###' - - names - - zaku - gufu - dom - - path - /usr/bin/env - - usr - - '### --- diclst --- ###' - - bin - - '### --- diclst --- ###' - - env - - python - - bar - '2' - - foo - 1 - - hoge - - hello - 999 - 12.3 $ cat tmp.yaml | ./diclst.py | diff tmp2.yaml - $
一致します。
それでは、前回の確認を。
compyting.yaml を PyYAMLでロードしたオブジェクトを、 PyYAMLでブロックスタイルに出力したのが blk.yaml、 同じオブジェクトをezyamlでダンプしたのが ezo.yaml でした。
$ cat computing.yaml | ./diclst.py > tmp3.yaml $ cat blk.yaml | ./diclst.py > tmp4.yaml $ cat ezo.yaml | ./diclst.py > tmp5.yaml
それぞれ変換かけたものを tmp3.yaml, tmp4.yaml, tmp5.yaml にして...
$ diff tmp3.yaml tmp4.yaml $
ここは当然一致。
$ diff tmp3.yaml tmp5.yaml 131,132c131,132 < - - 'False' < - 'True' --- > - - false > - true 155c155 < - 'False' --- > - false 1037,1038c1037,1038 < - - 'OFF' < - 'ON' --- > - - false > - true 1308,1309c1308,1309 < - - 'OFF' < - 'ON' --- > - - false > - true 2225c2225 < - null --- > - None 2240c2240 < - null --- > - None 2255c2255 < - null --- > - None 6960c6960,6963 < - '[15,30,45,60]' --- > - - 15 > - 30 > - 45 > - 60 6977c6980,6984 < - '[0.5,1.1,1.6,2.1,2.6]' --- > - - 0.5 > - 1.1 > - 1.6 > - 2.1 > - 2.6
ezyamlの出力側だけですが、なかなか差が少ないかな?
ezyamlはboolは関知してないのですが、 ここのは文字列としての'False', 'True'。 これはezyamlは文字列としてfalse, trueを出力してるのか?
いや、'OFF', 'ON'という文字列もfalse, trueで出してる。
blk.yaml中にも'True','False','OFF','ON'の文字列があるので、 オブジェクトの段階では文字列で保持されているはず。
diclst.pyで変換する前のblk.yamlとezo.yamlを見てみると...
blk.yaml : - name: dpm_ttic vars: - choices: - 'False' - 'True' : - choices: - 'OFF' - 'ON' choices_style: h desc: use_predict_pose desc sample ezo.yaml : - name: dpm_ttic vars: - kind: radio_box : choices: - False - True : choices: - OFF - ON desc: use_predict_pose desc sample
ああ〜。そういう事。
ezyaml.py は bool とは知らずに、文字列としてFalse, Trueをezo.yamlに出力。
diclst.py がPyYAMLを使ってそれをロードするときに、 False, TrueはたまたON, OFFという文字列は、boolの値として解釈される。 なるほど。
$ python >>> import yaml >>> yaml.dump(['True'], default_flow_style=False) "- 'True'\n" >>> yaml.dump(['true'], default_flow_style=False) "- 'true'\n" >>> yaml.dump(['TRUE'], default_flow_style=False) "- 'TRUE'\n" >>> yaml.dump(['TrUe'], default_flow_style=False) '- TrUe\n'
最後のだけ、出力が "- 'TrUe'\n" じゃなく、'- TrUe\n'。 つまり、あえてシングルクオートで囲まれていません。
PyYAMLがそう出力するという事は、PyYAMLでロードするときも、 TrUe は文字列として扱われるはずで...
True, true, TRUE は bool として扱ってしまうので、 明示的にシングルクオートで囲って文字列と主張していると。
>>> yaml.dump(['ON'], default_flow_style=False) "- 'ON'\n" >>> yaml.dump(['On'], default_flow_style=False) "- 'On'\n" >>> yaml.dump(['on'], default_flow_style=False) "- 'on'\n" >>> yaml.dump(['oN'], default_flow_style=False) '- oN\n' >>> yaml.dump(['OFF'], default_flow_style=False) "- 'OFF'\n" >>> yaml.dump(['Off'], default_flow_style=False) "- 'Off'\n" >>> yaml.dump(['off'], default_flow_style=False) "- 'off'\n" >>> yaml.dump(['oFf'], default_flow_style=False) '- oFf\n' >>> yaml.dump(['ofF'], default_flow_style=False) '- ofF\n'
これまた然り。
ezyamlの出力側だけ考えるなら、対応としては、 他の値になりうるキーワードを文字列として出力するときは、 シングルクオートで囲うべし。
今のところキーワードは [ True, true, TRUE, False, false, FALSE, ON, On, on, OFF, Off, off ]
次は、null のところが None。
blk.yaml - cmd_param: dash: '' delim: := desc: null label: 'accel_rate(m/s^2) :' name: accel_rate v: 1.0 ezo.yaml - desc: None v: 1.0 name: accel_rate cmd_param: dash: '' delim: ':=' label: 'accel_rate(m/s^2) :'
これは、単純に
ezyaml.py def dump_value(obj): if type(obj) == str: s = obj (q2, q1, cl) = map( lambda t: t in s, ('"', "'", ':') ) if q2 and q1: s = s.replace("'", "\\'") q1 = False if q2 or q1 or cl or get_value(s) != obj or s == '': qt = '"' if q1 else "'" s = qt + s + qt return s if type(obj) == list: return '[]' if type(obj) == dict: return '{}' return '{}'.format(obj)
ここで None なら 'null' を返せばよさそう。
次の...
< - '[15,30,45,60]' --- > - - 15 > - 30 > - 45 > - 60
blk.yaml, ezo.yamlの対応箇所は
blk.yaml - cmd_param: dash: '' delim: := desc: clustering_distances desc sample kind: str label: clustering_distances (no spaces) name: clustering_distances v: '[15,30,45,60]' ezo.yaml - kind: str name: clustering_distances v: [15,30,45,60] desc: clustering_distances desc sample cmd_param: dash: '' delim: ':=' label: clustering_distances (no spaces)
あ〜、なるほど。
ezyamlは、おお真面目に[15,30,45,60]を文字列として、 ezo.yaml に出力してるだけですね。
シングルクオートで囲う条件に、 フロースタイルのリスト形式など気にしてるはずもなく...
そして、これをdiclst.pyでPyYAMLがロードすると、リストと解釈してしまう。 なるほど。
この場合も対応は、文字列全体をシングルクオートで囲って出力するときの条件を追加ですね。
'['で始まり']'で終る場合と、辞書の'{'で始まり'}'で終る場合を追加で。
ついでに、ツール追加。
$ cat v6.patch | patch -p1 $ cat ioyaml.py #!/usr/bin/env python import sys import yaml import ezyaml if __name__ == "__main__": s = sys.stdin.read() o = ezyaml.load(s) if 'iez' in sys.argv else yaml.load(s) s = ezyaml.dump(o) if 'oez' in sys.argv else yaml.dump(o, default_flow_style=False) sys.stdout.write(s) # EOF $ $ chmod +x ioyaml.py $ cat computing.yaml | ./ioyaml.py | diff blk.yaml - $
確かに、引数なしで blk.py と同等。
$ cat computing.yaml | ./ioyaml.py oez > ezo2.yaml
これで、computing.yaml を PyYAMLでロードして、ezyamlでダンプした結果が ezo2.yaml に出るはず。
$ diff ezo.yaml ezo2.yaml 116c116 < v: False --- > v: 'False' 119,120c119,120 < - False < - True --- > - 'False' > - 'True' 564,565c564,565 < - OFF < - ON --- > - 'OFF' > - 'ON' 692,693c692,693 < - OFF < - ON --- > - 'OFF' > - 'ON' 1127c1127 < - desc: None --- > - desc: null 1134c1134 < - desc: None --- > - desc: null 1141c1141 < - desc: None --- > - desc: null 3377c3377 < v: [15,30,45,60] --- > v: '[15,30,45,60]' 3385c3385 < v: [0.5,1.1,1.6,2.1,2.6] --- > v: '[0.5,1.1,1.6,2.1,2.6]'
お。いい感じ。diclst.py を通さずとも、偶然いい感じ?
$ diff blk.yaml ezo2.yaml | head 2,5d1 < car_dpm: < param: car_dpm < car_fusion: < param: car_fusion 8,9c4,6 < pedestrian_dpm: < param: pedestrian_dpm --- > synchronization:
これは、順番が...
$ cat ezo2.yaml | ./diclst.py | diff tmp3.yaml - $
OK。ezyaml 出力側、いい感じです。
さてezyaml入力側。
$ cat blk.yaml | ./ioyaml.py iez > ezi.yaml
computing.yaml をPyYAMLでブロックスタイルにした blk.yaml を、 ezyaml でロードして、PyYAML でダンプしたものが ezi.yaml に出るはず。 (ややこしや)
$ head ezi.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: $ head blk.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion:
お。先頭は一致してる様子。
$ diff blk.yaml ezi.yaml | head 36c36 < v: false --- > v: 'false' 86c86 < v: true --- > v: 'true' 94c94 < v: false
そうか。 ezyaml入力側は、bool未対応。 trueやfalseを単なる文字列として扱うので、 オブジェクトの段階で文字列で保持する事になる。
その違いが、出力でシングルクオートの有無になって表れると。
C言語版でどうするかはさて置き、とりあえず入力側のbool対応するべしか。
ezyaml.py def get_value(s): try: return int(s) except ValueError: pass try: return float(s) except ValueError: pass if s == '[]': return [] if s == '{}': return {} if s == '': return None lst = parse(s) if len(lst) == 1 and s[:1] in qts: s = s[1:-1] return s
ここで、文字列としての'True'なら、引数sにシングルクオート付きでくるはず。
なので、例のキーワード一致なら、boolで返せばよさそう。
$ python >>> bool('False') True >>> eval('False') False >>> eval('OFF') Traceback (most recent call last): File "", line 1, in File " ", line 1, in NameError: name 'OFF' is not defined
そう甘くないか。
あと、出力側でNoneなら'null' 出すようにしたので、 入力側でもnull がきたら文字列じゃなくてNoneと解釈せねば。
$ cat v7.patch | patch -p1 $ cat blk.yaml | ./ioyaml.py iez | ./diclst.py | diff tmp3.yaml - $
入力側OK。v^_^)
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp3.yaml - $
オールOK。
ロード時に '#' 以降をコメント扱いにしてみます。
「クオート文字列中とエスケープ対応に注意」ですね。
ezyaml.py def parse(s): lst = [''] in_esc = False qt = None while s: (c, s) = ( s[:1], s[1:] ) if not in_esc and c == '\\': esc = True continue if in_esc: in_esc = False lst[-1] += c continue if qt: if c in qts: lst[-1] += c lst += '' qt = None continue elif c in qts: qt = c if lst[-1]: lst += '' lst[-1] += c while len(lst) > 1 and lst[-1] == '': lst.pop() return lst
このparse(s)が、地の文字列と、クオートで囲われた文字列に分解して、 リストで返してくれるので...
ezyaml.py def load(s): lines = s.strip().split('\n') lines = list( map(cut_tail_spc, lines) ) :
ここでlines作るときに、parseで判定しながら コメント部分を削除してみます。
元からの空行や、処理した結果の空行も、ここで削除でよさそうですね。
ck1.yaml にコメントや空行を追加した ck5.aml を用意してみました。
$ cat v8.patch | patch -p1 $ cat ck5.yaml # ck5.yaml foo: 1 bar: '2' # string ! hoge: - hello - 999 - 12.3 # commnet # comment # commnet 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: # GUNDAM - zaku - gufu - dom # EOF $ $ cat ck5.yaml | ./ezyaml.py 2> /dev/null 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom bar: '2' hoge: - hello - 999 - 12.3 foo: 1 $
それらしい。
$ cat ck1.yaml | ./diclst.py | tee tmp2.yaml - '### --- diclst --- ###' - - 1 - fuga - - 2 - guha - - 3 - - '### --- diclst --- ###' - - names - - zaku - gufu - dom - - path - /usr/bin/env - - usr - - '### --- diclst --- ###' - - bin - - '### --- diclst --- ###' - - env - - python - - bar - '2' - - foo - 1 - - hoge - - hello - 999 - 12.3 $ $ cat ck5.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp2.yaml - $
一致。OK。
正常系が通らなくなってやしないか、大きなファイルでも確認。
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp3.yaml - $
一致。OK。
TODO
では、ぼちぼちC言語版のコーディングを。
まずは、リストや辞書のC言語のライブラリ。 標準ライブラリ的なものには、おそらく無くて、 標準じゃない一長一短なライブラリは、それこそ星の数ほどある事でしょう。
などと言っておきながら、自らもその末席を汚します。
objs.[ch] でオブジェクトのリスト的なものを作ってみました。 辞書は未だですが、まぁキーと値の要素2のリストのリストにしようかと思案してます。
$ cat v9.patch | patch -p1 $ make gcc -Wall -g -c -o ezyaml.o ezyaml.c gcc -Wall -g -c -o objs.o objs.c gcc -Wall -g -o ezyaml ezyaml.o objs.o $ cat ck5.ayml # ck5.yaml foo: 1 bar: '2' # string ! hoge: - hello - 999 - 12.3 # commnet # comment # commnet 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: # GUNDAM - zaku - gufu - dom # EOF $ cat ck5.yaml | ./ezyaml "foo: 1" "bar: '2'" "hoge:" "- hello" "- 999" "- 12.3" "1: fuga" "2: guha" "3:" " path: /usr/bin/env" " usr:" " bin:" " env:" " - python" " names:" " - zaku" " - gufu" " - dom" foo: bar $
とりあえず、未だコメントと空行の削除を試してみてるだけです。;-p)
ezyaml.py を睨みながら粛々と ezyaml.c のコーディングを進めました。
適当にでっちあげた objs.[ch] ですが、文字列のところが、なかなかすっきりしません。
ezyaml.c に追加した cp_add() cp_replace() が、charのポインタだけで何とかなりそうな予感です。
これをヒントに objs.[ch] を書き換えたいことろですが、とりあえず動く状態を1つ作っておきます。
$ cat v10.patch | patch -p1 $ make clean $ make
簡単なYAMLデータでお試し。
$ cat ck1.yaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck1.yaml | ./ezyaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $
一致。
続いてコメント追加版で
$ cat ck5.yaml # ck5.yaml foo: 1 bar: '2' # string ! hoge: - hello - 999 - 12.3 # commnet # comment # commnet 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: # GUNDAM - zaku - gufu - dom # EOF $ cat ck5.yaml | ./ezyaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $
一致。OK。
続いて空のリスト、辞書、文字列を含むデータで
$ cat ck3.yaml foo: '' bar: '2' hoge: - [] - {} - '' 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' $ cat ck3.yaml | ./ezyaml foo: '' bar: '2' hoge: - [] - {} - '' 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $
一致。OK。
では、おもいきって computing.yaml で
$ cat computing.yaml | ./blk.py > blk.yaml $ head blk.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: $ cat blk.yaml | ./ezyaml | head - buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: $
だめ。全体をリストに解釈のパターン。原因は...
$ cat blk.yaml | ./ezyaml | grep '^-' | head - buttons: - is publishing - kind: checkbox - - - cmd_param: - - - name: waypoint_clicker - curve - label: Curve Recognize - Rth [m] - - - cmd_param: - - - name: waypoint_velocity_visualizer - areas. $ cat blk.yaml | ./ezyaml | grep -B10 -A10 '^- is pub' desc: save_filename desc sample kind: path label: Save File name: save_filename path_type: save v: /tmp/saved_waypoints.csv - - cmd_param: dash: '' delim: ':=' descs: When you save the velocity in each waypoints, Please check if /current_velocity - is publishing - kind: checkbox label: Save /current_velocity name: save_velocity v: False - - - cmd_param: dash: '' delim: ':=' desc: interval desc sample label: Interval name: interval
これまた例の文字列の折り返しの現象...
python版で対策して、それをC言語に直したつもりでしたが?
pyhon版
ezyaml.py def get_cont_str(idt, lines): if lines: s = lines[0] (idt_, kind_) = get_idt_kind(s) if kind_ == 'other' and idt_ == idt: # ! v = get_value( s[idt:] ) typ = type(v) if ( typ == str and v ) or ( typ in (int, float) ): s = lines.pop(0)[idt:] return ' ' + s + get_cont_str(idt, lines) return ''
呼び出し元は
def get_dict(idt, lines): s = lines.pop(0)[idt:] idx = find_colon_idx(s) (k, v) = ( s[:idx], s[idx+1:] ) k = get_value( k.strip() ) v = get_value( v.strip() ) if v == None: if lines: v = get_dict_v(idt, lines) elif type(v) == str: v += get_cont_str(idt+2, lines) :
C言語版
ezyaml.c struct obj * get_cont_str(int idt, struct obj *lines) { if( list_len(lines) ){ char *s = lines_top_str(lines); int idt_, kind_; get_idt_kind(s, &idt_, &kind_); if(kind_ == TYP_OTHER && idt_ == idt){ /* ! */ struct obj *v = get_value( slice_tail(s, idt) ); int typ = v->typ; if( ( typ == OBJ_TYP_STR && strlen( obj_buf(v) ) ) || ( typ == OBJ_TYP_INT || typ == OBJ_TYP_DOUBLE ) ){ struct obj *str = list_pop_top(lines); char *s = slice_tail( obj_buf(str), idt ); struct obj *add = get_cont_str(idt, lines); struct obj *ret = str_new(" "); ret = str_append(ret, s); ret = str_append( ret, obj_buf(add) ); obj_del(str); obj_del(add); return ret; } } } return str_new(""); }
うーむ。ぱっと見てpythonに比べて、かなり複雑。
呼び出し元は
struct obj * get_dict(int idt, struct obj *lines) { struct obj *str = list_pop_top(lines); char *s = obj_buf(str); int idx = find_colon_idx(s); struct obj *sk = str_new_n(s, idx); struct obj *sv = str_new( slice_tail(s, idx+1) ); struct obj *k, *v; struct obj *dic = dict_new(); obj_del(str); sk = str_strip(sk); k = get_value( obj_buf(sk) ); obj_del(sk); sv = str_strip(sv); v = get_value( obj_buf(sv) ); obj_del(sv); if(v->typ == OBJ_TYP_PTR && v->any.p == NULL){ if( list_len(lines) ){ v = get_dict_v(idt, lines); } }else if(v->typ == OBJ_TYP_STR){ struct obj *add = get_cont_str(idt, lines); v = str_append( v, obj_buf(add) ); obj_del(add); } :
デバッグするべし。
ひとつ別の問題も発見。
$ head -20 blk.yaml buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: param: pedestrian_fusion pedestrian_kf: param: pedestrian_kf synchronization: desc: synchronization desc sample run: roslaunch runtime_manager synchronization.launch name: Computing params: - name: sys vars: $ cat blk.yaml | ./ezyaml > ng.yaml $ head -20 ng.yaml - buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: param: pedestrian_fusion pedestrian_kf: param: pedestrian_kf synchronization: desc: synchronization desc sample run: roslaunch runtime_manager synchronization.launch name: Computing params: - - name: sys vars:
辞書の値がリストになっている箇所の params: の直後...
python版とC言語版。目をこらして目視diffしてみると、ここ!
python版
elif type(v) == str: v += get_cont_str(idt+2, lines)
C言語版
}else if(v->typ == OBJ_TYP_STR){ struct obj *add = get_cont_str(idt, lines); v = str_append( v, obj_buf(add) ); obj_del(add); }
idt+2 の箇所が idt になってました。orz
$ cat v11.patch diff -ur v10/ezyaml.c v11/ezyaml.c --- v10/ezyaml.c 2018-09-07 23:45:13.000000000 +0900 +++ v11/ezyaml.c 2018-09-08 16:51:20.000000000 +0900 @@ -267,7 +267,7 @@ v = get_dict_v(idt, lines); } }else if(v->typ == OBJ_TYP_STR){ - struct obj *add = get_cont_str(idt, lines); + struct obj *add = get_cont_str(idt+2, lines); v = str_append( v, obj_buf(add) ); obj_del(add); } $ cat v11.patch | patch -p1 $ make clean $ make
効果や如何に。
$ cat blk.yaml | ./ezyaml | head buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion:
お!
トップレベルがリストじゃなくて辞書になった。
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | head 19c19 < - name: sys --- > - - name: sys 21c21 < - desc: cpu_chks desc sample --- > - - desc: cpu_chks desc sample 27c27 < - desc: nice desc sample
ああ〜。やっぱり params: 直後の箇所。
カットアンドトライ
$ cat > ck6.yaml - foo - foo: bar - bar ^D $ cat ck6.yaml - foo - foo: bar - bar $ cat ck6.yaml | ./ezyaml - foo - - foo: bar - bar
これで現象が再現。
$ cat ck6.yaml | ./ioyaml.py - foo - foo: bar - bar $ cat ck6.yaml | ./ioyaml.py -iez -oez - foo - foo: bar - bar
python版だと問題なし。
さて、リストの要素が辞書のとき。
python版とC言語版の目視diff。 get_list()は問題なさそう。 get_dict()で ... !
ptyhon版
def get_dict(idt, lines): s = lines.pop(0)[idt:] idx = find_colon_idx(s) :
C言語版
struct obj * get_dict(int idt, struct obj *lines) { struct obj *str = list_pop_top(lines); char *s = obj_buf(str); int idx = find_colon_idx(s); :
sをidt分、進めてなかった。orz
$ cat v12.patch --- v11/ezyaml.c 2018-09-08 16:51:20.000000000 +0900 +++ v12/ezyaml.c 2018-09-08 17:12:53.000000000 +0900 @@ -244,7 +244,7 @@ get_dict(int idt, struct obj *lines) { struct obj *str = list_pop_top(lines); - char *s = obj_buf(str); + char *s = slice_tail( obj_buf(str), idt ); int idx = find_colon_idx(s); struct obj *sk = str_new_n(s, idx); $ cat v12.patch | patch -p1 $ make clean $ make
では、ck6.yaml から
$ cat ck6.yaml - foo - foo: bar - bar $ cat ck6.yaml | ./ezyaml - foo - foo: bar - bar
OK。
では computing.yaml どうか?
$ cat computing.yaml | ./blk.py | tee blk.yaml | ./ezyaml | head -20 buttons: car_dpm: param: car_dpm car_fusion: param: car_fusion car_kf: param: car_kf pedestrian_dpm: param: pedestrian_dpm pedestrian_fusion: param: pedestrian_fusion pedestrian_kf: param: pedestrian_kf synchronization: desc: synchronization desc sample run: roslaunch runtime_manager synchronization.launch name: Computing params: - name: sys vars: $
ふむ。先頭20行は、いい感じ。
$ cat blk.yaml | ./ezyaml | diff blk.yaml - 36c36 < v: false --- > v: False 70c70 < delim: := --- > delim: ':=' 81c81 < delim: := --- > delim: ':=' 86c86 < v: true --- > v: True 89c89 < delim: := --- > delim: ':=' 94c94 < v: false --- > v: False 97c97 < delim: := --- > delim: ':=' 105c105 < delim: := :
あら?
そもそもpython版では、こういうのあったかな?
$ cat blk.yaml | grep -i 'v: false' | sort | uniq v: false $ cat blk.yaml | ./ezyaml.py 2> /dev/null | grep -i 'v: false' | sort | uniq v: False
なんと。python版でも同じ現象でてた様子。
なんで? 以前の...
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp3.yaml - $
ああ〜。そうか diclist.py が ezyaml.py が出す False を false になおしてたのか。
True, False のdumpは true, false に修正すべし。
あと、文字列が ':' を含む場合でも辞書の右辺側なら、 あえてシングルクオートで囲まずともよいと?
$ grep ':.*:' blk.yaml | sort | uniq depend_bool: 'lambda v : v == 0' depend_bool: 'lambda v : v == 1' depend_bool: 'lambda v : v == 1' - cmd: roslaunch viewers viewers.launch viewer_type:=image_d_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=image_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=points_image_d_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=points_image_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=traffic_light_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=vscan_image_d_viewer - cmd: roslaunch viewers viewers.launch viewer_type:=vscan_image_viewer delim: := desc: Name of the topic containing the grid_map_msgs:GridMap containing the road label: 'Plane number: ' label: 'accel_rate(m/s^2) :' label: 'angle_error(degree) :' label: 'budget(ms):' label: 'lateral_accel_limit :' label: 'lowpass_gain_angular_z :' label: 'lowpass_gain_linear_x :' label: 'nice:' label: 'period(ms):' label: 'pitch:' label: 'position_error(m) :' label: 'prio:' label: 'roll:' label: 'x:' label: 'y:' label: 'yaw:' label: 'z:' - label: 'pitch:' - label: 'roll:' - label: 'x:' - label: 'y:' - label: 'yaw:' - label: 'z:'
とは限らないようですね。
blk.yaml のパターンでは、辞書の右側でも、 コロンの後にスペースがきてる場合と、 コロンで文字列が終わってる場合は、シングルクオート必要。
python版を含めて、dump_value() の呼び出し元が、 dump_dict()からの場合を特別扱いして、 ':'が含まれてたときの扱いを変えるようにしてみます。
$ cat v13.patch | patch -p1 $ make clean $ make
python版で辞書のオブジェクトの状態を経由すると、 順番が変わってしまうので、 C言語版確認してから、python版で問題の箇所だけを狙って確認すべしですかね。
$ cat ck1.yaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck1.yaml | ./ezyaml foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom $ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ $ cat ck1.yaml | ./ezyaml.py 2>/dev/null | diff ck1.yaml - $ $ cat ck3.yaml | ./ezyaml.py 2>/dev/null | diff ck3.yaml - $ $ cat ck5.yaml | ./ezyaml.py 2>/dev/null | diff ck1.yaml - $ $ cat ck6.yaml | ./ezyaml.py 2>/dev/null | diff ck6.yaml - $ $ cat ck3.yaml foo: '' bar: '2' hoge: - [] - {} - '' 1: [] 2: {} 3: path: /usr/bin/env usr: bin: env: [] names: - {} - [] - '' $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ $ cat ck5.yaml # ck5.yaml foo: 1 bar: '2' # string ! hoge: - hello - 999 - 12.3 # commnet # comment # commnet 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: # GUNDAM - zaku - gufu - dom # EOF $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ $ cat ck6.yaml - foo - foo: bar - bar $ cat ck6.yaml | ./ezyaml - foo - foo: bar - bar $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $
小さいデータの分にはpython版も、たまたま並びが変わらず確認が楽です。
では、大きいデータ
$ cat computing.yaml | ./blk.py > blk.yaml $ wc -l blk.yaml 4275 blk.yaml $ cat blk.yaml | ./ezyaml | wc -l 4262 $ cat blk.yaml | ./ezyaml | diff blk.yaml - | head 315c315 < v: 3.0 --- > v: 3 366c366 < max: 1.0 --- > max: 1 411,412c411,412 < max: 5.0 $ cat blk.yaml | ./ezyaml | diff blk.yaml - | wc -l 989
なかなか違いがありますな。
$ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | wc -l 69
え。python版だと違いが少ない...
$ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | head 1706c1706 < - Autoware's current_velocities --- > - "Autoware's current_velocities" 1788c1788 < - Autoware's current_velocities --- > - "Autoware's current_velocities" 2109,2110c2109 < descs: When you save the velocity in each waypoints, Please check if /current_velocity
どうやら「v: 3.0 が v: 3 に」とかの問題がC言語版だけ出てるという事ですな。
そして、単なるリストの要素が文字列の場合で、文字列中に文字としてのシングルクオートが含まれる場合。 辞書の場合の':'の判定のために、このケースは全部別の種類のクオートで囲うようにしてましたが、 実は辞書以外の場合はそれも不要なのか...
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | grep -i 'true' $ $ cat blk.yaml | ./ezyaml | diff blk.yaml - | grep -i 'false' $ $ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | grep -i 'false' $ $ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | grep -i 'true' $
True, Falseの違いの箇所は消えてる模様。
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | grep ':[^ ]' < desc: Name of the topic containing the grid_map_msgs:GridMap containing the road > desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas. $ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | grep ':[^ ]' < desc: Name of the topic containing the grid_map_msgs:GridMap containing the road > desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas.
文字列で':'直後にスペース以外がきてるときの違いを抽出してみると、 C言語版、python版、ともに同じ1箇所だけが、あぶり出されました。
でも、これは...
これは全体でクオートで囲う違いじゃなくて、PyYAMLでブロック形式で出力したときに、 長すぎる文字列を自動的に折り返すけど、ezyamlでは未対応なので違ってます。
TODO
では、"C言語版「v: 3.0 が v: 3 に」とかの問題"から。
load側では、
ezyaml.c struct obj * get_value(char *s) { int i; double d; char *e; struct obj *obj, *lst, *str; if(*s == '\0'){ void *vp = NULL; return obj_new(OBJ_TYP_PTR, &vp, 0); } i = (int) strtol(s, &e, 10); if(*e == '\0'){ return obj_new(OBJ_TYP_INT, &i, 0); } d = strtod(s, &e); if(*e == '\0'){ return obj_new(OBJ_TYP_DOUBLE, &d, 0); } :
「文字列全部が整数に変換できるか」を先に試してるので、 "3.0"の場合は整数にならないはずです。
オブジェクトとしてはちゃんと少数で保持されてるはず。
dump側では?
struct obj * dump_value(struct obj *obj, int dic_right) { : if(obj->typ == OBJ_TYP_DOUBLE){ char buf[1024]; sprintf(buf, "%g", obj->any.d); return str_new(buf); } return str_new(""); /* ! */ }
ああ、これだ。"%g"。
これが "3" を出してるはず。
%f だと "3.000000" 的な事になるだろうし...
自前でするなら (int)d == d で特別扱いすべしですかね。
まず、修正前の状態を確認。
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | grep -A3 '3\.0' | head < v: 3.0 --- > v: 3 366c366 -- < v: 3.0 --- > v: 3 439c439 -- $ echo 5.0 | ./ezyaml 5
修正かけてみてどうか?
$ cat v14.patch diff -ur v13/ezyaml.c v14/ezyaml.c --- v13/ezyaml.c 2018-09-08 18:08:45.000000000 +0900 +++ v14/ezyaml.c 2018-09-09 05:08:20.000000000 +0900 @@ -622,7 +622,12 @@ } if(obj->typ == OBJ_TYP_DOUBLE){ char buf[1024]; - sprintf(buf, "%g", obj->any.d); + double d = obj->any.d; + if( (int)d == d ){ + sprintf(buf, "%d.0", (int)d); + }else{ + sprintf(buf, "%g", d); + } return str_new(buf); } return str_new(""); /* ! */ $ cat v14.patch | patch -p1 $ make clean $ make $ echo 5.0 | ./ezyaml 5.0 $ cat blk.yaml | ./ezyaml | diff blk.yaml - | grep -A3 '3\.0' $
OK。
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | wc -l 69
これでpython版に追いついたはずです。
一応小さいデータでも
$ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $
OK。
TODO
さて、「辞書以外の文字列で文字としてのシングルクオートを含むだけの場合に限り、全体をダブルクオートで囲わずともよい問題」
そして、単なるリストの要素が文字列の場合で、文字列中に文字としてのシングルクオートが含まれる場合。 辞書の場合の':'の判定のために、このケースは全部別の種類のクオートで囲うようにしてましたが、 実は辞書以外の場合はそれも不要なのか...
と言う事だったのでとりあえず、dump_value() で辞書の右側じゃないときで、シングルクオート由来だけでダブルクオートで囲おうとしてる場合に、対策を入れるべしか。
まず、変更前の現象を確認。
$ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry $ cat ck7.yaml | ./ezyaml.py - "don't move" - "can't find, don't move" - "can't find": "don't move" - "can't find": "don't move, don't worry" - "can't find": - "don't move" - "don't worry" - "can't find": "don't move" "don't move": - "don't worry" $ cat ck7.yaml | ./ezyaml - - "don't move" - "can't find, don't move" - "can't find: don't move" - "can't find: don't move, don't worry" - "can't find:" - - "don't move" - "don't worry" - "can't find: don't move" - "don't move:" - - "don't worry"
なぬ! さらに別の問題発覚。 ck7.yamlでpython版とC言語版で結果が違ってます。
まず、こちらから
"can't find: don't move" だけで、もう違ってる?
$ echo "can't find: don't move" | ./ezyaml.py 2> /dev/null "can't find": "don't move" $ echo "can't find: don't move" | ./ezyaml "can't find: don't move"
違ってる。再現します。
ちなみに
$ echo "can't find: don't move" | ./blk.py can't find: don't move
ああ、なんか辞書の右に限らず、左もそうしないとダメなのか... まぁそれは後回し。
パースして':'探すあたりの動作が違うはず。 デバッグアウト入れて確認。
diff -ur v14/ezyaml.c dbg/ezyaml.c --- v14/ezyaml.c 2018-09-09 05:08:20.000000000 +0900 +++ dbg/ezyaml.c 2018-09-10 21:10:40.120626000 +0900 @@ -150,9 +150,15 @@ { int idx = 0; struct obj *lst = parse(s); +#if 1 + printf("len=%d\n", list_len(lst)); +#endif while( list_len(lst) > 0 ){ struct obj *str = list_pop_top(lst); s = obj_buf(str); +#if 1 + printf("s=\"%s\"\n", s); +#endif if( !is_qts(*s) && strchr(s, c) ){ return idx + ( strchr(s, c) - s ); } @@ -164,7 +170,13 @@ int find_colon_idx(char *s) { +#if 1 + int r = find_idx(s, ':'); + printf("colon=%d\n", r); + return r; +#else return find_idx(s, ':'); +#endif } #define TYP_OTHER (-1) diff -ur v14/ezyaml.py dbg/ezyaml.py --- v14/ezyaml.py 2018-09-08 18:08:45.000000000 +0900 +++ dbg/ezyaml.py 2018-09-10 21:13:11.020854000 +0900 @@ -44,14 +44,19 @@ def find_idx(s, c): idx = 0 lst = parse(s) + print('len={}'.format(len(lst))) # dbg for s in lst: + print("s=\"{}\"".format(s)) # dbg if ( s[:1] not in qts ) and ( c in s ): return idx + s.index(c) idx += len(s) return -1 def find_colon_idx(s): - return find_idx(s, ':') + r = find_idx(s, ':') # dbg + print('colon={}'.format(r)) # dbg + return r # dbg + #return find_idx(s, ':') def get_kind(s): if s.startswith('- '):
試してみると
$ echo "can't find: don't move" | ./ezyaml.py 2> /dev/null len=1 s="can't find: don't move" len=1 s="can't find: don't move" colon=10 len=1 s="can't find: don't move" colon=10 "can't find": "don't move" $ echo "can't find: don't move" | ./ezyaml len=3 s="can" s="'t find: don'" s="t move" colon=-1 "can't find: don't move"
いやいや。むしろ逆にC言語版の動作が期待したものになってる。
def parse(s): lst = [''] : if qt: if c in qts: lst[-1] += c lst += '' : elif c in qts: qt = c if lst[-1]: lst += ''
ここ! 「lst += ''」これでした orz。
しかし何故これで実行時のエラーなし?
$ python >>> lst = [''] >>> lst += '' >>> lst [''] >>> lst + '' Traceback (most recent call last): File "", line 1, in TypeError: can only concatenate list (not "str") to list >>> lst = lst + '' Traceback (most recent call last): File " ", line 1, in TypeError: can only concatenate list (not "str") to list
原因解りません。なぜか '+=' だと通ります。
で、やりたかったのは
>>> lst += [''] >>> lst ['', '']
この動作。
$ cat v15.patch diff -ur v14/ezyaml.py v15/ezyaml.py --- v14/ezyaml.py 2018-09-08 18:08:45.000000000 +0900 +++ v15/ezyaml.py 2018-09-10 21:16:31.000000000 +0900 @@ -28,13 +28,13 @@ if qt: if c in qts: lst[-1] += c - lst += '' + lst += [''] qt = None continue elif c in qts: qt = c if lst[-1]: - lst += '' + lst += [''] lst[-1] += c while len(lst) > 1 and lst[-1] == '': $ cat v15.patch | patch -p1 $ make clean $ make
これで何と言うか、python版がC言語版と同様に、「間違った状態」に足並みが揃ったはず。
$ cat ck7.yaml | ./ezyaml | tee ng - - "don't move" - "can't find, don't move" - "can't find: don't move" - "can't find: don't move, don't worry" - "can't find:" - - "don't move" - "don't worry" - "can't find: don't move" - "don't move:" - - "don't worry" $ cat ng - - "don't move" - "can't find, don't move" - "can't find: don't move" - "can't find: don't move, don't worry" - "can't find:" - - "don't move" - "don't worry" - "can't find: don't move" - "don't move:" - - "don't worry" $ cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ng - $
で、そもそもこの結果が
$ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry
から、かけ離れているという問題。切り出してみると
$ echo "can't find: don't move" | ./blk.py can't find: don't move $ echo "can't find: don't move" | ./ezyaml.py 2> /dev/null "can't find: don't move" $ echo "can't find: don't move" | ./ezyaml "can't find: don't move"
blk.pyの動作に対して、足並み揃って違ってると。
"can't find: don't move" の文字列を、 [ "can", "'tfind: don'", "t move" ] という3つの文字列にパースして、 クオートで囲われてない文字列中に':'が含まれるかの判定...
まさに最初の決め事の通り、意図した動作... ちょっと状況を整理しよう。
こうなるからこそ当初「"can't find": "don't move"」の形式で対応しようと決めたはず。
そこから、辞書の右側の文字列なら、':'含む場合でもクオートで囲わずとも識別可能ということで 「score: 99:100」を許して
今は、 「辞書以外の文字列で文字としてのシングルクオートを含むだけの場合に限り、全体をダブルクオートで囲わずともよい問題」 として対策を試そうとしていて、それ以前でひっかかっていると。
$ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry
というデータを作ってみて、何やらPyYAMLとは全然違う動作になっていて
3行目の「- can't find: don't move」を切り出して「can't find: don't move」を試して思案している状況。
簡単なところから順に確認していきます。
$ echo "score: 99:100" | ./blk.py score: 99:100 $ echo "score: 99:100" | ./ezyaml.py 2> /dev/null score: 99:100 $ echo "score: 99:100" | ./ezyaml score: 99:100 $ $ echo "- \"can't find\": \"don't move\"" - "can't find": "don't move" $ echo "- \"can't find\": \"don't move\"" | ./blk.py - can't find: don't move $ echo "- \"can't find\": \"don't move\"" | ./ezyaml.py 2> /dev/null - '"can\'t find": "don\'t move"' $ echo "- \"can't find\": \"don't move\"" | ./ezyaml Segmentation fault (core dumped)
はぅ。
当初の想定通り辞書の左も右もダブルクオートで囲って文字列と主張し、 ':'の箇所は囲ってないのに、ezyaml.py で辞書として解釈されてないばかりか...
C言語版はSegfault。orz
ezyaml.py def find_idx(s, c): idx = 0 lst = parse(s) for s in lst: print("s='{}'".format(s)) #dbg if ( s[:1] not in qts ) and ( c in s ): return idx + s.index(c) idx += len(s) return -1
パース結果を確認
$ echo "- \"can't find\": \"don't move\"" | ./ezyaml.py 2> /dev/null s='- ' s='"can'' s='t find' s='": "' s='don' s=''t move"' :
こりゃだめだ。"...' とか '..." で切り出してる。
def parse(s): : while s: (c, s) = ( s[:1], s[1:] ) : if qt: #if c in qts: if c == qt: lst[-1] += c lst += [''] qt = None continue
に修正
$ echo "- \"can't find\": \"don't move\"" | ./ezyaml.py 2> /dev/null s='- ' s='"can't find"' s=': ' s='"don't move"' :
C言語版も
parse(char *s) { : while(*s){ int c = *s++; : if(qt){ /*if( is_qts(c) ){*/ if(c == qt){ buf = parse_append(buf, c); buf = parse_append(buf, '\0'); list_append( lst, str_new( obj_buf(buf) ) ); buf->buf_n = 0; qt = 0; continue; } :
に修正
$ echo "- \"can't find\": \"don't move\"" | ./ezyaml - "can't find": "don't move"
Segfault消えた。
次、辞書の右側でクオート外してみた場合では?$ echo "- \"can't find\": don't move" - "can't find": don't move $ echo "- \"can't find\": don't move" | ./blk.py - can't find: don't move $ echo "- \"can't find\": don't move" | ./ezyaml.py 2> /dev/null s='- ' s='"can't find"' s=': don' s=''t move' : - "can't find": "don't move" $ echo "- \"can't find\": don't move" | ./ezyaml - "can't find": "don't move"
一応、':'が地の文字列に入っているので、loadでそこは辞書と解釈されてます。
ただし、dump側で当初の決め事通りにクオートで囲ったままでした。
単なる文字列で文字としてシングルクオートを含む場合
$ echo "- don't move" - don't move $ echo "- don't move" | ./blk.py - don't move $ echo "- don't move" | ./ezyaml.py 2> /dev/null s='- don' s=''t move' : - "don't move" $ echo "- don't move" | ./ezyaml - "don't move"
この場合もパースで分かれても、まとめて文字列になるのでloadはOK。
dumpでは同様にクオートで囲ったまま。この場合は囲わずともよし。
結局ここでの対応は、 「辞書の左のキー以外の文字列で、 シングルクオートだけの要因でダブルクオートで囲って出力しようとしてるときは、 別にダブルクオートで囲わなくても平気」
となるとクオートの種類が逆の関係もあって
$ echo "- say \"good-bye\"" - say "good-bye" $ echo "- say \"good-bye\"" | ./blk.py - say "good-bye" $ echo "- say \"good-bye\"" | ./ezyaml.py 2> /dev/null s='- say ' s='"good-bye"' : - 'say "good-bye"' $ echo "- say \"good-bye\"" | ./ezyaml - 'say "good-bye"'
まとめると「辞書の左のキー以外の文字列で、 クオート要因のために全体を別の種類のクオートで囲って出力 しようとしてるときは、別に全体を囲わなくても平気」対応
$ cat v16.patch | patch -p1 $ make clean $ make
「変更の狙い」そのものから確認
$ echo "- \"can't find\": \"don't move\"" | ./ezyaml.py 2> /dev/null - "can't find": don't move $ echo "- \"can't find\": \"don't move\"" | ./ezyaml - "can't find": don't move $ echo "- \"can't find\": don't move" | ./ezyaml.py 2> /dev/null - "can't find": don't move $ echo "- \"can't find\": don't move" | ./ezyaml - "can't find": don't move $ echo "- don't move" | ./ezyaml.py 2> /dev/null - don't move $ echo "- don't move" | ./ezyaml - don't move $ echo "- say \"good-bye\"" | ./ezyaml.py 2> /dev/null - say "good-bye" $ echo "- say \"good-bye\"" | ./ezyaml - say "good-bye"
OK。
では、computing.yaml。
$ cat computing.yaml | ./blk.py > blk.yaml $ cat blk.yaml | ./ezyaml | diff blk.yaml - | wc -l 61 $ cat blk.yaml | ./ezyaml | diff blk.yaml - | head 2109,2110c2109 < descs: When you save the velocity in each waypoints, Please check if /current_velocity < is publishing --- > descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing 2186,2187c2185 < desc: When the estimated turning radius > Rth [m], the area is recognized as a < curve --- > desc: When the estimated turning radius > Rth [m], the area is recognized as a curve
違いが減りました。どうやら行の折り返しの違いが残っただけ。
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | diff blk.yaml - | wc -l 6888 $ cat blk.yaml | ./ezyaml.py 2> /dev/null | diff blk.yaml - | head 1a2,6 > car_kf: > param: car_kf > synchronization: > run: roslaunch runtime_manager synchronization.launch > desc: synchronization desc sample 3a9,10 > pedestrian_kf: > param: pedestrian_kf 6,7d12
python版は例によって、辞書の順番が保証されないのでdiffでの直接比較は意味なくて...
$ cat blk.yaml | ./diclst.py > tmp3.yaml $ head tmp3.yaml - '### --- diclst --- ###' - - buttons - - '### --- diclst --- ###' - - car_dpm - - '### --- diclst --- ###' - - param - car_dpm - - car_fusion - - '### --- diclst --- ###' - - param $ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp3.yaml - $
これは一致するものの、diclst.py が最後に PyYAML 使って dump してるので正確な確認になってなくて...
修正前の問題となった違いに絞って確認すべし
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | head 315c315 < v: 3.0 --- > v: 3 366c366 < max: 1.0 --- > max: 1 411,412c411,412 < max: 5.0$ cat blk.yaml | ./ezyaml.py 2>/dev/null | diff blk.yaml - | head 1706c1706 < - Autoware's current_velocities --- > - "Autoware's current_velocities" 1788c1788 < - Autoware's current_velocities --- > - "Autoware's current_velocities" 2109,2110c2109 < descs: When you save the velocity in each waypoints, Please check if /current_velocity
とかだったので
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | grep 'v: 3$' $ $ cat blk.yaml | ./ezyaml.py 2> /dev/null | grep 'v: 3\.0$' v: 3.0 v: 3.0 v: 3.0 v: 3.0 $ cat blk.yaml | ./ezyaml.py 2> /dev/null | grep "\- \"Autoware's\"" $ $ cat blk.yaml | ./ezyaml.py 2> /dev/null | grep "\- Autoware's" - Autoware's current_velocities - Autoware's current_velocities
まぁ...OK。
$ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry $ cat ck7.yaml | ./blk.py | diff ck7.yaml - $ $ cat ck7.yaml | ./ezyaml.py 2> /dev/null - - don't move - can't find, don't move - "can't find: don't move" - "can't find: don't move, don't worry" - "can't find:" - - don't move - don't worry - "can't find: don't move" - "don't move:" - - don't worry $ cat ck7.yaml | ./ezyaml - - don't move - can't find, don't move - "can't find: don't move" - "can't find: don't move, don't worry" - "can't find:" - - don't move - don't worry - "can't find: don't move" - "don't move:" - - don't worry
ck7.yaml。PyYAMLは平気で解釈できてるし、そのように出力しなおすけども... ezyamlの「簡易な」処理ではハードルが高いっす。
辞書の左だけはクオートで囲って緩くしてみると
$ cat ck8.yaml - don't move - can't find, don't move - "can't find": don't move - "can't find": don't move, don't worry - "can't find": - don't move - don't worry - "can't find": don't move "don't move": - don't worry $ cat ck8.yaml | ./ezyaml.py 2> /dev/null - don't move - can't find, don't move - "can't find": don't move - "can't find": don't move, don't worry - "can't find": - don't move - don't worry - "can't find": don't move "don't move": - don't worry $ cat ck8.yaml | ./ezyaml.py 2> /dev/null | diff ck8.yaml - $ $ cat ck8.yaml | ./ezyaml - don't move - can't find, don't move - "can't find": don't move - "can't find": don't move, don't worry - "can't find": - don't move - don't worry - "can't find": don't move "don't move": - don't worry $ cat ck8.yaml | ./ezyaml | diff ck8.yaml - $
これだとOK。
一応小さいデータで一通り
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml.py 2> /dev/null | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - $ cat ck8.yaml | ./ezyaml.py 2> /dev/null | diff ck8.yaml - $ $ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $ cat ck8.yaml | ./ezyaml | diff ck8.yaml - $
OK。
TODO
辞書の左側のキー文字列中にクオートを含んでいる場合でも、全体を別の種類のクオートで囲わなくても良いようにしたいなぁ...
ということで検討してみます。
もともと何でキー全体を囲ってたか?
can't find: don't move now '99:100' scoreこの2つは機械から見ると、ほぼ同じ構成。
[ can ][ 't find: don' ][ t move ] [ now ][ '99:100' ][ socre ]
can't find: don't move
は辞書を期待
now '99:100' score
は文字列を期待
区別できないので
"can't find": "don't move"
なら辞書
can't find: don't move now '99:100' score
なら文字列という道を選んだはず。
ところがPyYAMLは
can't find: don't move
を辞書としてロードする。
すごい!
now '99:100' score
と同じ構成なのに、区別してるのか ... って、マテヨ。
now '99:100' score
を文字列としてロードするとは限らんのでは...?
確認しやすいように blk.py 更新してオプション -f を追加してみました。 -f つきで実行すると、フロースタイルでdumpします。(blk.pyなのに)
$ cat v17.patch diff -ur v16/blk.py v17/blk.py --- v16/blk.py 2018-08-31 22:53:08.000000000 +0900 +++ v17/blk.py 2018-09-11 22:36:57.000000000 +0900 @@ -3,9 +3,11 @@ import sys import yaml +flow = '-f' in sys.argv + s = sys.stdin.read() o = yaml.load(s) -s = yaml.dump(o, default_flow_style=False) +s = yaml.dump(o, default_flow_style=flow) sys.stdout.write(s) # EOF $ cat v17.patch | patch -p1
確かめてみましょう。
$ echo "- can't find: don't move" - can't find: don't move $ echo "- can't find: don't move" | ./blk.py -f [{can't find: don't move}]
確かに辞書で...
$ echo "- now '99:100' score" | ./blk.py -f ['now ''99:100'' score']
やはりこれは文字列なのか
辞書の方の':'直後のスペースとってみると
$ echo "- can't find:don't move" - can't find:don't move $ echo "- can't find:don't move" | ./blk.py -f ['can''t find:don''t move']
文字列に!?
逆に':'の直後にスペース付けてみると
$ echo "- now '99: 100' score" - now '99: 100' score $ echo "- now '99: 100' score" | ./blk.py -f [{now '99: 100' score}]
辞書になりました!?
え、ちょっと。基本的な勘違いがあった?
$ echo "- foo: bar" | ./blk.py -f [{foo: bar}] $ echo "- foo:bar" | ./blk.py -f ['foo:bar']
まじで?
$ echo ":a" | grep ':[^ ]' :a $ echo ": " | grep ':[^ ]' $
なので
$ cat computing.yaml | grep ':[^ ]' | sort | uniq delim: ':=' delim : ':=' cmd : roslaunch viewers viewers.launch viewer_type:=image_d_viewer cmd : roslaunch viewers viewers.launch viewer_type:=image_viewer cmd : roslaunch viewers viewers.launch viewer_type:=points_image_d_viewer cmd : roslaunch viewers viewers.launch viewer_type:=points_image_viewer cmd : roslaunch viewers viewers.launch viewer_type:=traffic_light_viewer cmd : roslaunch viewers viewers.launch viewer_type:=vscan_image_d_viewer cmd : roslaunch viewers viewers.launch viewer_type:=vscan_image_viewer delim : ':=' delim : ':=' delim : ':=' delim : ':=' delim : ':=' desc : Name of the topic containing the grid_map_msgs:GridMap containing the road areas. label : 'budget(ms):' label : 'nice:' label : 'period(ms):' label : 'prio:' label : 'accel_rate(m/s^2) :' label : 'angle_error(degree) :' label : 'lateral_accel_limit :' label : 'lowpass_gain_angular_z :' label : 'lowpass_gain_linear_x :' label : 'position_error(m) :' label : 'pitch:' label : 'roll:' label : 'x:' label : 'y:' label : 'yaw:' label : 'z:' # label : 'Layer:' # label : 'Max:' # label : 'Min:' $
ガーン。辞書としての':'の直後はスペースか改行が必要という結論。
改めて仕様を調べてみると確かに。orz
でも、でも、pythonでは
$ python >>> {1:2,3:4} {1: 2, 3: 4} >>> {'foo':True, 'bar':False} {'foo': True, 'bar': False}
':'直後のスペースは無くともええのです。 勘違いしてました。orz
PyYAMLの動作。色々試してみます。
$ echo "- 'hello: world'" - 'hello: world' $ echo "- 'hello: world'" | ./blk.py -f ['hello: world'] #文字列 $ echo "- hi'hello: world'" | ./blk.py -f [{hi'hello: world'}] #辞書 $ echo "- hi 'hello: world'" | ./blk.py -f [{hi 'hello: world'}] #辞書 $ echo "- hi 'hello: world' wide " | ./blk.py -f [{hi 'hello: world' wide}] #辞書 $ echo "- hi 'hello world' wide " | ./blk.py -f [hi 'hello world' wide] #文字列 $ echo "- hi 'hello:world' wide " | ./blk.py -f ['hi ''hello:world'' wide'] #文字列 $ echo "- hi 'hello:world'" | ./blk.py -f ['hi ''hello:world'''] #文字列 $ echo "- 'hello:world'" | ./blk.py -f ['hello:world'] #文字列 $ echo "- hello:world" | ./blk.py -f ['hello:world'] #文字列 $ echo "- hello: world" | ./blk.py -f [{hello: world}] #辞書 $ echo "- 'hello: world'" | ./blk.py -f ['hello: world'] #文字列 $ echo "- 'hello: world' wide" | ./blk.py -f Traceback (most recent call last): File "./blk.py", line 9, in^o = yaml.load(s) File "/usr/lib/python2.7/dist-packages/yaml/__init__.py", line 71, in load return loader.get_single_data() File "/usr/lib/python2.7/dist-packages/yaml/constructor.py", line 37, in get_single_data node = self.get_single_node() File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 36, in get_single_node document = self.compose_document() File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 55, in compose_document node = self.compose_node(None, None) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 82, in compose_node node = self.compose_sequence_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 110, in compose_sequence_node while not self.check_event(SequenceEndEvent): File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 98, in check_event self.current_event = self.state() File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 393, in parse_block_sequence_entry "expected , but found %r" % token.id, token.start_mark) yaml.parser.ParserError: while parsing a block collection in " ", line 1, column 1: - 'hello: world' wide ^ expected , but found ' ' in " ", line 1, column 18: - 'hello: world' wide
最後のエラーはさておき、
くらいの基本ルールだろうか?
となると、辞書のキー文字列に': 'とコロンスペースを含めたい場合とかは?
$ python >>> import yaml >>> dic = {"can't find": 1} >>> dic {"can't find": 1} >>> yaml.dump(dic, default_flow_style=False) "can't find: 1\n" >>> k = "99 : 100" >>> d = { k: True } >>> d {'99 : 100': True} >>> yaml.dump(d, default_flow_style=False) "'99 : 100': true\n" >>> yaml.dump({k: 'abc'}, default_flow_style=False) "'99 : 100': abc\n" >>> ^D $ echo "'99 : 100': abc" | ./blk.py -f {'99 : 100': abc}
この場合、基本ルールで解釈すると1つ目の': 'で辞書と思ってしまうので、
キー 「'99」の文字列 値 「100': abc」の文字列 という辞書
として解釈してしまいそう...
「先頭にクオートがあった場合」を特別に扱ってる気がする。
先頭にクオートがあった場合は、 エスケープされてない同じ種類のクオートが出現するまでを文字列と解釈でしょうか。
$ echo "- 'abc\"def'" - 'abc"def' $ echo "- 'abc\"def'" | ./blk.py -f [abc"def] $ echo "- 'foo: bar'" - 'foo: bar' $ echo "- 'foo: bar'" | ./blk.py -f ['foo: bar']
は文字列
$ echo "- hoge 'foo: bar'" - hoge 'foo: bar' $ echo "- hoge 'foo: bar'" | ./blk.py -f [{hoge 'foo: bar'}]
は辞書
先頭('- 'の直後)がクオートで始まってないから、 「hoge 'foo」 がキー文字列の辞書と解釈されて、 値は 「bar'」という文字列
基本ルール
$ echo "- 'hellow' world" - 'hellow' world $ echo "- 'hellow' world" | ./blk.py -f Traceback (most recent call last): File "./blk.py", line 9, ino = yaml.load(s) : yaml.parser.ParserError: while parsing a block collection :
これは本家PyYAMLも許さないのか...
$ echo "- \"'hellow' world\"" - "'hellow' world" $ echo "- \"'hellow' world\"" | ./blk.py -f ['''hellow'' world'] $ echo "- \"hellow\" world" - "hellow" world $ echo "- \"hellow\" world" | ./blk.py -f Traceback (most recent call last): File "./blk.py", line 9, ino = yaml.load(s) : yaml.parser.ParserError: while parsing a block collection : $ echo "- '\"hellow\" world'" - '"hellow" world' $ echo "- '\"hellow\" world'" | ./blk.py -f ['"hellow" world']
辞書じゃなければ、クオートで始まれば、必ずそのクオートで終れと。
ということで、これまでの parse(s) --> list は、一部流用していいかも。
listの最初の文字列がクオートで囲われていたらそれは除外。 それ以外の文字列に': 'が無く、末尾が':'で終ってなければ、 全体をjoinした文字列に、という甘々仕様でいいでしょ。
PyYAMLはフロースタイルも考慮してるから、厳しいのかも知れませんね。
TODO
では 「辞書の場合のパースを': '(コロン+スペース)を基本として抜本的に見直すべし」 から。
load側は、辞書の':'の判定を': 'にして、行末だけ':'で判定。 parse(s)結果のlistの先頭のときだけクオート文字列なら除外すべし。
dump側
基本的に文字列でクオートで囲む必要があるのは
さらに辞書のキー文字列のときは': 'を含むとき、':'で終るときもクオートで囲む。
(こんなシンプルで良かったのかな?)
$ cat v18.patch | patch -p1 $ make clean $ make
では、肝心のところから簡単に確認。
$ echo "foo: bar" foo: bar $ echo "foo: bar" | ./blk.py foo: bar $ echo "foo: bar" | ./blk.py -f {foo: bar} $ echo "foo: bar" | ./ezyaml.py foo: bar {'foo': 'bar'} $ echo "foo: bar" | ./ezyaml foo: bar $ echo "- foo: bar" - foo: bar $ echo "- foo: bar" | ./blk.py - foo: bar $ echo "- foo: bar" | ./blk.py -f [{foo: bar}] $ echo "- foo: bar" | ./ezyaml.py - foo: bar [{'foo': 'bar'}] $ echo "- foo: bar" | ./ezyaml - foo: bar $ echo "- foo:bar" - foo:bar $ echo "- foo:bar" | ./blk.py - foo:bar $ echo "- foo:bar" | ./blk.py -f ['foo:bar'] $ echo "- foo:bar" | ./ezyaml.py - foo:bar ['foo:bar'] $ echo "- foo:bar" | ./ezyaml - foo:bar
OK。
では、クオート系をいくつか
$ echo "can't find: dot't move" can't find: dot't move $ echo "can't find: dot't move" | ./blk.py can't find: dot't move $ echo "can't find: dot't move" | ./blk.py -f {can't find: dot't move} $ echo "can't find: dot't move" | ./ezyaml.py can't find: dot't move {"can't find": "dot't move"} $ echo "can't find: dot't move" | ./ezyaml can't find: dot't move $ echo "- now '99:100' score" - now '99:100' score $ echo "- now '99:100' score" | ./blk.py - now '99:100' score $ echo "- now '99:100' score" | ./blk.py -f ['now ''99:100'' score'] $ echo "- now '99:100' score" | ./ezyaml.py - now '99:100' score ["now '99:100' score"] $ echo "- now '99:100' score" | ./ezyaml - now '99:100' score
PyYAMLの出すフロースタイルと、pythonのダンプ形式とが、微妙にクオートが違ってます。 要素が文字列の要素数1のリストとしてロードされて、 ここでの本題の「ブロックスタイルの出力」としては一致してるのでOK。
キー文字列を甘めにしたck8.yaml
$ cat ck8.yaml - don't move - can't find, don't move - "can't find": don't move - "can't find": don't move, don't worry - "can't find": - don't move - don't worry - "can't find": don't move "don't move": - don't worry $ cat ck8.yaml | ./blk.py - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry $ cat ck8.yaml | ./blk.py -f [don't move, 'can''t find, don''t move', {can't find: don't move}, {can't find: 'don''t move, don''t worry'}, {can't find: [don't move, don't worry, {can't find: don't move, don't move: [don't worry]}]}] $ cat ck8.yaml | ./ezyaml.py - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry ["don't move", "can't find, don't move", {"can't find": "don't move"}, {"can't find": "don't move, don't worry"}, {"can't find": ["don't move", "don't worry", {"can't find": "don't move", "don't move": ["don't worry"]}]}] $ cat ck8.yaml | ./ezyaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry
これまた -f のフロースタイルと、ptyonのダンプがちょっと違うものの、ブロックスタイルとしては一致。
ということは、厳しめだったck7.yamlもいけるのでは?
$ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry $ cat ck7.yaml | ./blk.py - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry $ cat ck7.yaml | ./blk.py | diff ck7.yaml - $ $ cat ck7.yaml | ./blk.py -f [don't move, 'can''t find, don''t move', {can't find: don't move}, {can't find: 'don''t move, don''t worry'}, {can't find: [don't move, don't worry, {can't find: don't move, don't move: [don't worry]}]}] $ cat ck7.yaml | ./ezyaml.py - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry ["don't move", "can't find, don't move", {"can't find": "don't move"}, {"can't find": "don't move, don't worry"}, {"can't find": ["don't move", "don't worry", {"can't find": "don't move", "don't move": ["don't worry"]}]}] $ cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - $ $ cat ck7.yaml | ./ezyaml | diff ck7.yaml - $
OK。
一応小さいデータで一通り
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,18 > bar: '2' > hoge: > - hello > - 999 > - 12.3 > foo: 1 $ cat ck3.yaml | ./ezyaml.py 2> /dev/null | diff ck3.yaml - 1,6d0 < foo: '' < bar: '2' < hoge: < - [] < - {} < - '' 17a12,17 > bar: '2' > hoge: > - [] > - {} > - '' > foo: '' $ cat ck5.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,18 > bar: '2' > hoge: > - hello > - 999 > - 12.3 > foo: 1 $ cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - $ $ cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - $ $ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $ cat ck7.yaml | ./ezyaml | diff ck7.yaml - $
python版の前半だけ並び順が...。 まぁ明らかに辞書の出力順が違うだけなのでOK。
computing.yamlで
$ cat computing.yaml | ./blk.py > blk.yaml $ cat blk.yaml | ./diclst.py > tmp3.yaml $ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py Traceback (most recent call last): File "./diclst.py", line 22, ino = yaml.load(s) File "/usr/lib/python2.7/dist-packages/yaml/__init__.py", line 71, in load return loader.get_single_data() File "/usr/lib/python2.7/dist-packages/yaml/constructor.py", line 37, in get_single_data node = self.get_single_node() File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 36, in get_single_node document = self.compose_document() File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 55, in compose_document node = self.compose_node(None, None) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 84, in compose_node node = self.compose_mapping_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 133, in compose_mapping_node item_value = self.compose_node(node, item_key) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 82, in compose_node node = self.compose_sequence_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 111, in compose_sequence_node node.value.append(self.compose_node(node, index)) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 84, in compose_node node = self.compose_mapping_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 133, in compose_mapping_node item_value = self.compose_node(node, item_key) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 82, in compose_node node = self.compose_sequence_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 111, in compose_sequence_node node.value.append(self.compose_node(node, index)) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 84, in compose_node node = self.compose_mapping_node(anchor) File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 127, in compose_mapping_node while not self.check_event(MappingEndEvent): File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 98, in check_event self.current_event = self.state() File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 428, in parse_block_mapping_key if self.check_token(KeyToken): File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 116, in check_token self.fetch_more_tokens() File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 220, in fetch_more_tokens return self.fetch_value() File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 576, in fetch_value self.get_mark()) yaml.scanner.ScannerError: mapping values are not allowed here in " ", line 73, column 16: label: nice:
!?
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | less 29c29 < label: 'nice:' --- > label: nice: 48c48 < label: 'prio:' --- > label: prio: 53c53 < label: 'period(ms):' --- > label: period(ms): 58c58 < label: 'budget(ms):' --- > label: budget(ms): 469c469 < label: 'Plane number: ' --- > label: Plane number: 487c487 < label: 'x:' --- > label: x: 491c491 < label: 'y:' --- > label: y: 495c495 < label: 'z:' --- > label: z: 499c499 < label: 'roll:' --- > label: roll: 503c503 < label: 'pitch:' --- > label: pitch: 507c507 < label: 'yaw:' --- > label: yaw: : 2109,2110c2109 < descs: When you save the velocity in each waypoints, Please check if /current_velocity < is publishing --- > descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing 2186,2187c2185 < desc: When the estimated turning radius > Rth [m], the area is recognized as a < curve --- > desc: When the estimated turning radius > Rth [m], the area is recognized as a curve : $ cat blk.yaml | ./ezyaml | diff blk.yaml - | wc -l 253
なんと、辞書の値側の文字列中に':'あった時の扱い
$ echo "foo: bar: hoge" foo: bar: hoge $ echo "foo: bar: hoge" | ./blk.py Traceback (most recent call last): File "./blk.py", line 9, ino = yaml.load(s) : yaml.scanner.ScannerError: mapping values are not allowed here in " ", line 1, column 9: foo: bar: hoge ^ $ echo "foo: 'bar: hoge'" | ./blk.py foo: 'bar: hoge' $ echo "foo: 'bar: hoge'" | ./blk.py -f {foo: 'bar: hoge'} $ echo "foo: bar:" foo: bar: $ echo "foo: bar:" | ./blk.py Traceback (most recent call last): File "./blk.py", line 9, in o = yaml.load(s) : yaml.scanner.ScannerError: mapping values are not allowed here in " ", line 1, column 9: foo: bar: ^ $ echo "foo: 'bar:'" | ./blk.py foo: 'bar:' $ echo "foo: 'bar:'" | ./blk.py -f {foo: 'bar:'}
PyYAMLに合わせるならば、辞書の値側の文字列も': 'を含む場合や終端':'の場合はクオートで囲わねば...。
$ echo "foo: bar: hoge" | ./ezyaml.py foo: bar: hoge {'foo': 'bar: hoge'} $ echo "foo: bar: hoge" | ./ezyaml foo: bar: hoge $ echo "foo: bar:" | ./ezyaml.py foo: bar: {'foo': 'bar:'} $ echo "foo: bar:" | ./ezyaml foo: bar:
なんだけどなぁ...
TODO
辞書の右側の文字列中に':'を含む場合のクオート対応
$ cat v19.patch diff -ur v18/ezyaml.c v19/ezyaml.c --- v18/ezyaml.c 2018-09-12 13:10:23.625370000 +0900 +++ v19/ezyaml.c 2018-09-12 15:01:51.947085000 +0900 @@ -580,7 +580,7 @@ if( is_not_same_str(s, obj) ){ return 1; } - if( from_dic == 'l' && ( strstr(s, ": ") || s[ strlen(s)-1 ] == ':' ) ){ + if( from_dic && ( strstr(s, ": ") || s[ strlen(s)-1 ] == ':' ) ){ return 1; } return 0; diff -ur v18/ezyaml.py v19/ezyaml.py --- v18/ezyaml.py 2018-09-12 13:12:18.634616000 +0900 +++ v19/ezyaml.py 2018-09-12 15:01:51.950200000 +0900 @@ -234,7 +234,7 @@ if get_value(s) != obj: return True - if from_dic == 'l' and (': ' in s or s[-1:] == ':'): + if from_dic and (': ' in s or s[-1:] == ':'): return True return False $ cat v19.patch | patch -p1 $ make clean $ make
では確認。
$ echo "foo: bar: hoge" | ./ezyaml.py foo: 'bar: hoge' {'foo': 'bar: hoge'} $ echo "foo: bar: hoge" | ./ezyaml foo: 'bar: hoge' $ echo "foo: bar:" | ./ezyaml.py foo: 'bar:' {'foo': 'bar:'} $ echo "foo: bar:" | ./ezyaml foo: 'bar:'
出力にクオートついて
入力がクオートついてても
$ echo "foo: 'bar: hoge'" foo: 'bar: hoge' $ echo "foo: 'bar: hoge'" | ./blk.py foo: 'bar: hoge' $ echo "foo: 'bar: hoge'" | ./blk.py -f {foo: 'bar: hoge'} $ echo "foo: 'bar: hoge'" | ./ezyaml.py foo: 'bar: hoge' {'foo': 'bar: hoge'} $ echo "foo: 'bar: hoge'" | ./ezyaml foo: 'bar: hoge' $ echo "foo: 'bar:'" foo: 'bar:' $ echo "foo: 'bar:'" | ./blk.py foo: 'bar:' $ echo "foo: 'bar:'" | ./blk.py -f {foo: 'bar:'} $ echo "foo: 'bar:'" | ./ezyaml.py foo: 'bar:' {'foo': 'bar:'} $ echo "foo: 'bar:'" | ./ezyaml foo: 'bar:'
OK。
$ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $ cat ck7.yaml | ./ezyaml | diff ck7.yaml - $ $ cat ck1.yaml | ./ioyaml.py | tee tmp_ck1.yaml 1: fuga 2: guha 3: names: - zaku - gufu - dom path: /usr/bin/env usr: bin: env: - python bar: '2' foo: 1 hoge: - hello - 999 - 12.3 $ cat ck1.yaml | ./ioyaml.py iez 1: fuga 2: guha 3: names: - zaku - gufu - dom path: /usr/bin/env usr: bin: env: - python bar: '2' foo: 1 hoge: - hello - 999 - 12.3 $ cat ck1.yaml | ./ioyaml.py iez | diff tmp_ck1.yaml - $ $ cat ck1.yaml | ./ioyaml.py oez 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom bar: '2' hoge: - hello - 999 - 12.3 foo: 1 $ cat ck1.yaml | ./ioyaml.py oez | diff tmp_ck1.yaml - 4,7d3 < names: < - zaku < - gufu < - dom 12a9,12 > names: > - zaku > - gufu > - dom 14d13 < foo: 1 18a18 > foo: 1
出力側で順が変わってしまうが内容的にはOK。
computing.yaml
$ cat blk.yaml | ./ezyaml | diff blk.yaml - | wc -l 61 $ cat blk.yaml | ./ezyaml | diff blk.yaml - | less 2109,2110c2109 < descs: When you save the velocity in each waypoints, Please check if /current_velocity < is publishing --- > descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing 2186,2187c2185 < desc: When the estimated turning radius > Rth [m], the area is recognized as a < curve --- > desc: When the estimated turning radius > Rth [m], the area is recognized as a curve 2933,2934c2931 < desc: Name of the topic containing the grid_map_msgs:GridMap containing the road < areas. --- > desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas. 2942,2943c2939 < desc: Name of the layer in the topic wayarea_topic_src that containes the road < areas. --- > desc: Name of the layer in the topic wayarea_topic_src that containes the road areas. 3006,3007c3002 < desc: Number of maximum iterations before using GNSS or stopping the localziation. < Default 32 --- > desc: Number of maximum iterations before using GNSS or stopping the localziation. Default 32 3016,3017c3011 < desc: Difference between consecutive scores to consider a safe match. Default < 14.0 --- > desc: Difference between consecutive scores to consider a safe match. Default 14.0 :
折り返しの違いだけが残るけど、若干行数減った?
$ cat blk.yaml | ./diclst.py > tmp3.yam $ cat blk.yaml | ./ezyaml.py 2> /dev/null | ./diclst.py | diff tmp3.yaml - $
python版でも diclst.py を通すと diclst.py が折り返しをするので一致。
TODO
長い文字列をPyYAMLと同様の位置で折り返してdump出来るように、 目指してみます。
computing.yaml
: params : - name : waypoint_saver vars : - name : save_filename descs : When you save the velocity in each waypoints, Please check if /current_velocity is publishing :
PyYAMLでloadして、PyYAMLでブロックスタイルでdumpしてみると
$ cat computing.yaml | ./blk.py > blk.yaml
blk.yaml
: - name: waypoint_saver vars: - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing :
てな具合に折り返されるのですが...
$ cat > ck9.yaml - name: waypoint_saver vars: - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing ^D $ $ cat ck9.yaml - name: waypoint_saver vars: - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing $ cat ck9.yaml | ./blk.py - name: waypoint_saver vars: - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing $ cat ck9.yaml | ./ezyaml.py 2> /dev/null - name: waypoint_saver vars: - descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing cmd_param: dash: '' delim: := $ cat ck9.yaml | ./ezyaml - name: waypoint_saver vars: - cmd_param: dash: '' delim: := descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing
!? 最後のC言語版だけ "is publishing"の前にスペース2つ?
という事はload側のはず
ezyaml.py
def get_cont_str(idt, lines): if lines: s = lines[0] (idt_, kind_) = get_idt_kind(s) if kind_ == 'other' and idt_ == idt: # ! v = get_value( s[idt:] ) typ = type(v) if ( typ == str and v ) or ( typ in (int, float) ): s = lines.pop(0)[idt:] return ' ' + s + get_cont_str(idt, lines)
に対して、ezyaml.c は
struct obj * get_cont_str(int idt, struct obj *lines) { if( list_len(lines) ){ char *s = lines_top_str(lines); int idt_, kind_; get_idt_kind(s, &idt_, &kind_); if(kind_ == TYP_OTHER && idt_ == idt){ /* ! */ struct obj *v = get_value( slice_tail(s, idt) ); int typ = v->typ; if( ( typ == OBJ_TYP_STR && strlen( obj_buf(v) ) ) || ( typ == OBJ_TYP_INT || typ == OBJ_TYP_DOUBLE ) ){ struct obj *str = list_pop_top(lines); char *s = slice_tail( obj_buf(str), idt ); struct obj *add = get_cont_str(idt, lines); struct obj *ret = str_new(" "); :
最後の行 " " がスペース1つ余計でした。
さて例えば、折り返してる行は
10 20 30 40 50 60 70 80 90 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789 descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing descs: When you save the velocity in each waypoints, Please check if /current_velocity is publishing desc: When the estimated turning radius > Rth [m], the area is recognized as a curve desc: When the estimated turning radius > Rth [m], the area is recognized as a curve desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas. desc: Name of the topic containing the grid_map_msgs:GridMap containing the road areas. desc: Name of the layer in the topic wayarea_topic_src that containes the road areas. desc: Name of the layer in the topic wayarea_topic_src that containes the road areas. desc: Number of maximum iterations before using GNSS or stopping the localziation. Default 32 desc: Number of maximum iterations before using GNSS or stopping the localziation. Default 32 desc: Difference between consecutive scores to consider a safe match. Default 14.0 desc: Difference between consecutive scores to consider a safe match. Default 14.0 :
1つ目の例が、折り返して90文字になってるのに対して...
2つ目の例が、88文字でも長いとされて折り返しが入って82文字になってる。
86文字でも長いとされて81文字に。
とりあえず、「何文字以下になるまで単語を折り返す」という原理じゃなさそう。
折り返す前の、末尾の単語の開始位置と、折り返した後の、末尾の単語の開始位置に注目してみると
このサンプリングデータだけ見る限りは「末尾の単語の開始位置が82以下になるまで、折り返す」なんだろうか?
とりあえずそれでお試し
ezyaml.c にバグ1つ発見。修正しておきます。
: @@ -29,11 +29,20 @@ r = cp_add( r, strdup(to) ); s = t + strlen(from); } - r = cp_add(r, s); + r = cp_add( r, strdup(s) ); free(bak); /* ! */ return r; } :
$ cat v20.patch | patch -p1 $ make clean $ make
さて、computing.yaml
$ cat computing.yaml | ./blk.py > blk.yaml $ cat blk.yaml | ./ezyaml | diff blk.yaml - $
OK。
python版の確認は困難かも。
$ cat blk.yaml | ./ezyaml.py 2> /dev/null | diff blk.yaml - | head 1a2,6 > car_kf: > param: car_kf > synchronization: > run: roslaunch runtime_manager synchronization.launch > desc: synchronization desc sample 3a9,10 > pedestrian_kf: > param: pedestrian_kf 6,7d12
まぁ、python版作ってから、それを見て作ったC言語版がOKなので...
一応小さいデータで一通り
$ cat ck1.yaml | ./ezyaml | diff ck1.yaml - $ cat ck3.yaml | ./ezyaml | diff ck3.yaml - $ cat ck5.yaml | ./ezyaml | diff ck1.yaml - $ cat ck6.yaml | ./ezyaml | diff ck6.yaml - $ cat ck7.yaml | ./ezyaml | diff ck7.yaml - $
C言語版OK。
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null | diff ck1.yaml - 1,6d0 < foo: 1 < bar: '2' < hoge: < - hello < - 999 < - 12.3 18a13,18 > bar: '2' > hoge: > - hello > - 999 > - 12.3 > foo: 1
うーん。
$ sort ck1.yaml | tee sort_ck1.yaml - python env: bin: - dom - gufu - zaku names: path: /usr/bin/env usr: - 12.3 - 999 - hello 1: fuga 2: guha 3: bar: '2' foo: 1 hoge: $ sort ck3.yaml | tee sort_ck3.yaml env: [] bin: - '' - [] - {} names: path: /usr/bin/env usr: - '' - [] - {} 1: [] 2: {} 3: bar: '2' foo: '' hoge:
などとしておいて..
$ cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - $ cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - $ cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - $ cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - $ cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - $ $ cat ck6.yaml - foo - foo: bar - bar $ cat ck7.yaml - don't move - can't find, don't move - can't find: don't move - can't find: don't move, don't worry - can't find: - don't move - don't worry - can't find: don't move don't move: - don't worry
まぁ、OKということで
これから何回も同じ確認手順をするだろうから、スクリプトにしておきます。
$ cat cklst.sh #!/bin/bash [ -e blk.yaml ] || cat computing.yaml | ./blk.py > blk.yaml [ -e sort_blk.yaml ] || sort blk.yaml > sort_blk.yaml [ -e sort_ck1.yaml ] || sort ck1.yaml > sort_ck1.yaml [ -e sort_ck3.yaml ] || sort ck3.yaml > sort_ck3.yaml while read line; do echo $line echo $line | sh [ $? -ne 0 ] && echo NG done >>EOL cat blk.yaml | ./ezyaml | diff blk.yaml - cat ck1.yaml | ./ezyaml | diff ck1.yaml - cat ck3.yaml | ./ezyaml | diff ck3.yaml - cat ck5.yaml | ./ezyaml | diff ck1.yaml - cat ck6.yaml | ./ezyaml | diff ck6.yaml - cat ck7.yaml | ./ezyaml | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_blk.yaml - cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - EOL # EOF $ $ chmod +x cklst.sh $ ./cklst.sh cat blk.yaml | ./ezyaml | diff blk.yaml - cat ck1.yaml | ./ezyaml | diff ck1.yaml - cat ck3.yaml | ./ezyaml | diff ck3.yaml - cat ck5.yaml | ./ezyaml | diff ck1.yaml - cat ck6.yaml | ./ezyaml | diff ck6.yaml - cat ck7.yaml | ./ezyaml | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_blk.yaml - cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - $
TODO
「objs.[ch] の文字列の処理をもうちょっとすっきりさせたいなぁ...」 の対応を試してみてます。
objのbuf[]の配列を廃止して、バッフアや文字列はポインタで管理するようにしてみました。
かなり大量に書き換えてます。
$ cat v21.patch | patch -p1 $ make clean $ make
他、python版の結果の比較では、単純にソートしただけではダメでした。
- foo: bar hoge: fuga
が
- hoge: fuga foo: bar
の順で出力されていたとすると、ソートで
hoge: fuga - foo: bar
と
foo: bar - hoge: fuga
... どこまで「確認」になるのか疑問ですが、安直に'-'をスペースに置換してからソートしてみます。
パッチにcklst.shの差分を含めてます。 cklst.shを実行すると、スクリプトsort_for_cklstを生成します。
$ rm -f sort_blk.yaml $ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - cat ck3.yaml | ./ezyaml | diff ck3.yaml - cat ck5.yaml | ./ezyaml | diff ck1.yaml - cat ck6.yaml | ./ezyaml | diff ck6.yaml - cat ck7.yaml | ./ezyaml | diff ck7.yaml - cat blk.yaml | ./ezyaml | diff blk.yaml - cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $ $ cat sort_for_cklst sed -e 's/\(^ *\)-\( [^ ]*:\)/\1 \2/' | sort
他いろいろ試してると、ちょいちょい free() でエラーが出たり、出なかったり...
どこかで、オーバーアクセスやメモリリークしてるもようです。orz
TODO
「どこかで、オーバーアクセスやメモリリークしてるもよう」 という事で、malloc, free の記録を残して表示する仕組みを入れて試してみます。
$ cat v22.patch | patch -p1 $ make clean $ DBG=-DMEM_DBG make gcc -Wall -DMEM_DBG -c -o ezyaml.o ezyaml.c gcc -Wall -DMEM_DBG -c -o objs.o objs.c gcc -Wall -DMEM_DBG -c -o chain.o chain.c gcc -Wall -DMEM_DBG -c -o mem.o mem.c gcc -Wall -DMEM_DBG -o ezyaml ezyaml.o objs.o chain.o mem.o
mem.[ch]を追加してみました。
$ cat mem.h #ifndef __MEM_H__ #define __MEM_H__ 1 void *mem_malloc(int sz, const char *filename, int line, const char *func); char *mem_strdup(const char *s, const char *filename, int line, const char *func); char *mem_strndup(const char *s, int n, const char *filename, int line, const char *func); void mem_free(void *p, const char *filename, int line, const char *func); void mem_show(void); #ifdef MEM_DBG #define malloc(sz) mem_malloc(sz, __FILE__, __LINE__, __func__) #define strdup(s) mem_strdup(s, __FILE__, __LINE__, __func__) #define strndup(s, n) mem_strndup(s, n, __FILE__, __LINE__, __func__) #define free(p) mem_free(p, __FILE__, __LINE__, __func__) #else #define mem_show() #endif #endif
Makefileに環境変数DBGを見る細工を追加したので、 -DMEM_DBG を指定してビルドすると、 mem.hをインクルードしてるソース中の malloc, strdup, strndup, free の呼び出し箇所が、 デバッグ用の関数呼び出しに置き換わります。
普通にmakeすると、mem_show()の呼び出し箇所は、逆に消えます。
mem.[ch]でリストのチェーン構造を使うので、 いっそ chain.[ch] に切り出して、 objs.[ch]のリスト構造を使う箇所は、 chain.[ch]を参照するように変更してみました。
chain.[ch]の中でmalloc, freeを使ってしまうと、 「卵が先かニワトリが先か」になるので、 chain.[ch]では、鎖の繋ぎ変えだけの処理を担当させてます。
ezyaml.c の最後に追加した mem_show() でfree漏れを表示してみます。
#include "objs.h" +#include "mem.h" void err(char *t, char *s) @@ -680,6 +681,9 @@ /* obj --> stderr ... */ obj_del(obj); + + mem_show(); + return 0; }
まずは簡単なところから
$ cat ck1.yaml | ./ezyaml |& head --- show --- 0x85b1a08 28 malloc objs.c 104 obj_new() 0x85b15e8 28 malloc objs.c 104 obj_new() 0x85b1ae0 28 malloc objs.c 104 obj_new() 0x85b1f08 28 malloc objs.c 104 obj_new() 0x85b1c70 28 malloc objs.c 104 obj_new() 0x85b1888 28 malloc objs.c 104 obj_new() 0x85b1608 28 malloc objs.c 104 obj_new() 0x85b15a0 28 malloc objs.c 104 obj_new() 0x85b1788 28 malloc objs.c 104 obj_new()
解放漏れ、ありあすぎ...
objs.c 104行目 objs_new() の中
struct obj * obj_new(int typ, void *vp) { struct obj *obj = malloc( sizeof(*obj) ); obj_init(obj, typ, vp); return obj; }
他の箇所は
: 0x9602a30 28 malloc objs.c 104 obj_new() 0x9602a50 28 malloc objs.c 104 obj_new() 0x9603258 28 malloc objs.c 104 obj_new() 0x9602900 4 strdup objs.c 284 str_new() 0x9603080 28 malloc objs.c 104 obj_new() 0x96031e8 28 malloc objs.c 104 obj_new() 0x9602f18 4 strdup objs.c 284 str_new() 0x9602fb8 28 malloc objs.c 104 obj_new() 0x9602028 28 malloc objs.c 104 obj_new() 0x9602978 28 malloc objs.c 104 obj_new() foo: 1 bar: '2' hoge: - hello - 999 - 12.3 1: fuga 2: guha 3: path: /usr/bin/env usr: bin: env: - python names: - zaku - gufu - dom
objs.c 284行目 str_new() からの strdup() で 4バイト。 なので、3文字の文字列。
struct obj * str_new(char *s) { s = strdup(s); return obj_new(OBJ_TYP_STR, &s); }
手掛かりが無さ過ぎかも...
$ grep obj_new objs.c obj_new(int typ, void *vp) struct obj *obj = obj_new(OBJ_TYP_BUF, &vp); return obj_new(OBJ_TYP_STR, &s); return obj_new(OBJ_TYP_LIST, &objs);
buf_new(), str_new(), list_new() から
$ grep obj_new ezyaml.c return obj_new(OBJ_TYP_PTR, &vp); return obj_new(OBJ_TYP_PTR, &vp); return obj_new(OBJ_TYP_INT, &i); return obj_new(OBJ_TYP_DOUBLE, &d); return obj_new(OBJ_TYP_BOOL, &i); return obj_new(OBJ_TYP_BOOL, &i); return obj_new(OBJ_TYP_PTR, &vp);
get_dict_v()の最後でnull用の値を返すところと、 get_value()の値として返すところ。
$ grep 'str_new[^_]' objs.c str_new(char *s) str = str_new(s); str = str_new(s); struct obj *r = str_new("");
str_new_n(), lines_new(), lines_join() から。 という事は str_new_n() も容疑者。 最後のlines_join()は1バイト確保なので除外。
$ grep str_new objs.c str_new(char *s) str_new_n(char *s, int n) str = str_new(s); struct obj *str = str_new_n( obj_ptr(buf), buf->n ); str = str_new_n( s, (t-s) ); str = str_new(s); struct obj *r = str_new("");
str_new_n(), str_read(), lines_new() から。
$ grep str_new ezyaml.c list_append( lst, str_new( obj_ptr(buf) ) ); struct obj *sk = str_new_n(t, idx); struct obj *sv = str_new( slice_tail(t, idx+1) ); return str_new_n(s, n); str = str_new(sum); list_append( lines, str_new(s) ); str = str_new(s); return str_new("[]"); return str_new("{}"); return str_new("null"); return str_new(obj->any.i ? "true" : "false"); return str_new(buf); return str_new(buf); return str_new(""); /* ! */ str = wrap_line( str_new(s), idt);
parse_buf_to_lst(), get_dict(), get_value(), wrap_line()
dump_list(), dump_value(), dump_other()
ただし、
return str_new("[]"); return str_new("{}"); return str_new("null"); return str_new(obj->any.i ? "true" : "false");
は3文字じゃないので除外。
$ cat ck1.yaml | ./ezyaml 2>&1 > /dev/null | wc -l 74 $ cat ck1.yaml | ./ezyaml 2>&1 > /dev/null | grep malloc | wc -l 70 $ cat ck1.yaml | ./ezyaml 2>&1 > /dev/null | grep -v malloc --- show --- 0x965a900 4 strdup objs.c 284 str_new() 0x965af18 4 strdup objs.c 284 str_new() $
mem.[ch]のmem_show()から、呼び出し元の関数名について登録済のコールバック関数を呼び出して、 解放漏れのアドレスにある情報を、ある程度表示するようにしてみました。
$ cat v23.patch | patch -p1 $ make clean $ DBG=-DMEM_DBG make $ cat ck1.yaml | ./ezyaml |& less --- show --- 0x8e9ea20 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e600 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9eaf8 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9ef20 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9ec88 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e8a0 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e620 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e5b8 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e7a0 28 malloc objs.c 169 obj_new() typ=list objs.n=0 0x8e9e548 28 malloc objs.c 169 obj_new() typ=list objs.n=0 : 0x8e9d270 28 malloc objs.c 169 obj_new() typ=str str=''2'' 0x8e9c900 4 strdup objs.c 358 str_new() 0x8e9d098 28 malloc objs.c 169 obj_new() typ=list objs.n=1 0x8e9d200 28 malloc objs.c 169 obj_new() typ=str str=''2'' :
出た出た。 要素数が0になったリストが大量に残っているのと、 '2'という3文字の文字列。 ezyaml.c で list_len(lst) の条件でループしてる箇所を見ると...
int find_idx(char *s, char *t) { int idx = 0; struct obj *lst = parse(s); s = lines_top_str(lst); if( s && is_qts(s[0]) ){ s = lines_pop_str(lst); idx += strlen(s); free(s); } while( list_len(lst) > 0 ){ s = lines_pop_str(lst); if( strstr(s, t) ){ idx += ( strstr(s, t) - s ); free(s); return idx; } idx += strlen(s); free(s); } return -1; }
parse()で生成したlstを、list_len(lst) が 0 になるまでループして、 そのままか... orz
struct obj * wrap_line(struct obj *str, int idt) { struct obj *lst; : lst = lines_new(s); free(s); obj_del(str); sum = strdup(""); s = lines_pop_str(lst); while( list_len(lst) ){ : } sum = cp_add(sum, s); str = str_new(sum); free(sum); return str; }
これまた然り。
とりあえず対策。
$ cat v24.patch | patch -p1 $ make clean $ DBG=-DMEM_DBG make $ cat ck1.yaml | ./ezyaml > /dev/null --- show --- 0x95f5790 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f56b0 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5670 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f55f0 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f54a8 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f5da8 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5460 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f5400 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5380 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5048 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5008 28 malloc objs.c 169 obj_new() typ=ptr p=(nil) 0x95f61b0 28 malloc objs.c 169 obj_new() typ=dict objs.n=0 0x95f5080 28 malloc objs.c 169 obj_new() typ=dict objs.n=0
'2'は消えましたな。
PTRでNULLは None で "null" 対応箇所があやしく、 DICTで要素数0は、'{}'?
とりあえず1つ見つけました。
struct obj * get_dict(int idt, struct obj *lines) { : str_strip(sv); v = get_value( obj_ptr(sv) ); obj_del(sv); if(v->typ == OBJ_TYP_PTR && v->any.p == NULL){ if( list_len(lines) ){ v = get_dict_v(idt, lines); } :
vがPTRでNULLのときに get_dict_v()でvを上書き。 元のv、解放漏れてます。
あと、空の辞書が残る場合とは?
struct obj * get_dict(int idt, struct obj *lines) { : if( list_len(lines) ){ char *s = lines_top_str(lines); int idt_, kind_; get_idt_kind(s, &idt_, &kind_); if(kind_ == OBJ_TYP_DICT && idt_ == idt){ struct obj *add = get_dict(idt, lines); while( dict_len(add) ){ dict_add_lst_kv( dic, dict_pop_top(add) ); } } } return dic; }
dict_len(add)でループでdict_pop_top(add)しつつ... からのdictになったaddを解放せずに終わってた orz
$ cat v25.patch | patch -p1 $ make clean $ DBG=-DMEM_DBG make $ cat ck1.yaml | ./ezyaml > /dev/null --- show --- $
OK。
ではcklst.sh
$ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck3.yaml | ./ezyaml | diff ck3.yaml - --- show --- cat ck5.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck6.yaml | ./ezyaml | diff ck6.yaml - --- show --- cat ck7.yaml | ./ezyaml | diff ck7.yaml - --- show --- cat blk.yaml | ./ezyaml | diff blk.yaml - --- show --- 0x7fe1f16a8dd0 48 malloc objs.c 169 obj_new() typ=str str='Works better with GNSS.' 0x7fe1f16a5c40 24 strdup objs.c 358 str_new() 0x7fe1f17226e0 48 malloc objs.c 169 obj_new() typ=str str='in Computing/Semantics. default. grid_map_wayarea' 0x7fe1f1723510 50 strdup objs.c 358 str_new() 0x7fe1f16120e0 48 malloc objs.c 169 obj_new() typ=str str='Objects outside the road will be filtered.' 0x7fe1f16a1680 43 strdup objs.c 358 str_new() 0x7fe1f1611400 48 malloc objs.c 169 obj_new() typ=str str='frame coordinate system.' 0x7fe1f1694fe0 25 strdup objs.c 358 str_new() 0x7fe1f16a06c0 48 malloc objs.c 169 obj_new() typ=str str='frame coordinate system.' 0x7fe1f169e580 25 strdup objs.c 358 str_new() 0x7fe1f1696100 48 malloc objs.c 169 obj_new() typ=str str='Default 2' 0x7fe1f1695eb0 10 strdup objs.c 358 str_new() 0x7fe1f1695ec0 48 malloc objs.c 169 obj_new() typ=str str='be employed. If reinitialization fails the algorithm will stop after n secs.' 0x7fe1f1695ff0 77 strdup objs.c 358 str_new() 0x7fe1f17e8540 48 malloc objs.c 169 obj_new() typ=double d=14 0x7fe1f17e7350 48 malloc objs.c 169 obj_new() typ=str str='Default 32' 0x7fe1f17e6e70 11 strdup objs.c 358 str_new() 0x7fe1f17203b0 48 malloc objs.c 169 obj_new() typ=str str='areas.' 0x7fe1f17e10c0 7 strdup objs.c 358 str_new() 0x7fe1f17e0400 48 malloc objs.c 169 obj_new() typ=str str='areas.' 0x7fe1f17e0b80 7 strdup objs.c 358 str_new() 0x7fe1f17b28a0 48 malloc objs.c 169 obj_new() typ=str str='curve' 0x7fe1f17b2080 6 strdup objs.c 358 str_new() 0x7fe1f17aa3e0 48 malloc objs.c 169 obj_new() typ=str str='is publishing' 0x7fe1f17a9e50 14 strdup objs.c 358 str_new() cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $
blk.yamlでやってみるとSTRの解放忘れがが未だどこかに。
blk.yaml
- cmd: roslaunch lidar_localizer ndt_matching_monitor.launch desc: Monitors NDT localizer. Halts or resets localization if algorithm fails. Works better with GNSS.
ということは文字列の折り返し箇所があやしい...
char * get_cont_str(int idt, struct obj *lines) { if( list_len(lines) ){ char *s = lines_top_str(lines); int idt_, kind_; get_idt_kind(s, &idt_, &kind_); if(kind_ == TYP_OTHER && idt_ == idt){ /* ! */ struct obj *v = get_value( slice_tail(s, idt) ); int typ = v->typ; if( ( typ == OBJ_TYP_STR && strlen( obj_ptr(v) ) ) || ( typ == OBJ_TYP_INT || typ == OBJ_TYP_DOUBLE ) ){ char *s = lines_pop_str(lines); char *t = strdup( slice_tail(s, idt) ); free(s); char *add = get_cont_str(idt, lines); return cp_add( cp_add( strdup(" "), t ), add ); } } } return strdup(""); }
get_value() 結果を v に保持して... 解放せず。orz
$ cat v26.patch | patch -p1 $ make clean $ DBG=-DMEM_DBG make $ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck3.yaml | ./ezyaml | diff ck3.yaml - --- show --- cat ck5.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck6.yaml | ./ezyaml | diff ck6.yaml - --- show --- cat ck7.yaml | ./ezyaml | diff ck7.yaml - --- show --- cat blk.yaml | ./ezyaml | diff blk.yaml - --- show --- cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $
OK。
$ make clean $ make gcc -Wall -c -o ezyaml.o ezyaml.c gcc -Wall -c -o objs.o objs.c gcc -Wall -c -o chain.o chain.c gcc -Wall -c -o mem.o mem.c gcc -Wall -o ezyaml ezyaml.o objs.o chain.o mem.o $ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - cat ck3.yaml | ./ezyaml | diff ck3.yaml - cat ck5.yaml | ./ezyaml | diff ck1.yaml - cat ck6.yaml | ./ezyaml | diff ck6.yaml - cat ck7.yaml | ./ezyaml | diff ck7.yaml - cat blk.yaml | ./ezyaml | diff blk.yaml - cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $
TODO
「ezyaml.cをライブラリとツールのmainの部分に分離」しておきます。
ezyaml.cのmain()関数はezyaml_main.cに切り出し。
ezyaml.hを追加して、ezyaml.cで公開しなくていい関数や変数はstaticに。
objs.[ch]に少しだけ関数を追加して、ezyaml.cから使うように少しだけ変更してみました。
$ cat v27.patch | patch -p1 $ make clean $ make gcc -Wall -c -o ezyaml_main.o ezyaml_main.c gcc -Wall -c -o ezyaml.o ezyaml.c gcc -Wall -c -o objs.o objs.c gcc -Wall -c -o chain.o chain.c gcc -Wall -c -o mem.o mem.c rm -rf libezyaml.a ar cq libezyaml.a ezyaml.o objs.o chain.o mem.o gcc -Wall -o ezyaml ezyaml_main.o libezyaml.a $ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - cat ck3.yaml | ./ezyaml | diff ck3.yaml - cat ck5.yaml | ./ezyaml | diff ck1.yaml - cat ck6.yaml | ./ezyaml | diff ck6.yaml - cat ck7.yaml | ./ezyaml | diff ck7.yaml - cat blk.yaml | ./ezyaml | diff blk.yaml - cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $
OK。
メモリリークも確認
$ make clean $ DBG=-DMEM_DBG make gcc -Wall -DMEM_DBG -c -o ezyaml_main.o ezyaml_main.c gcc -Wall -DMEM_DBG -c -o ezyaml.o ezyaml.c gcc -Wall -DMEM_DBG -c -o objs.o objs.c gcc -Wall -DMEM_DBG -c -o chain.o chain.c gcc -Wall -DMEM_DBG -c -o mem.o mem.c rm -rf libezyaml.a ar cq libezyaml.a ezyaml.o objs.o chain.o mem.o gcc -Wall -DMEM_DBG -o ezyaml ezyaml_main.o libezyaml.a $ ./cklst.sh cat ck1.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck3.yaml | ./ezyaml | diff ck3.yaml - --- show --- cat ck5.yaml | ./ezyaml | diff ck1.yaml - --- show --- cat ck6.yaml | ./ezyaml | diff ck6.yaml - --- show --- cat ck7.yaml | ./ezyaml | diff ck7.yaml - --- show --- cat blk.yaml | ./ezyaml | diff blk.yaml - --- show --- cat ck1.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck3.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck3.yaml - cat ck5.yaml | ./ezyaml.py 2> /dev/null | sort | diff sort_ck1.yaml - cat ck6.yaml | ./ezyaml.py 2> /dev/null | diff ck6.yaml - cat ck7.yaml | ./ezyaml.py 2> /dev/null | diff ck7.yaml - cat blk.yaml | ./ezyaml.py 2> /dev/null | ./sort_for_cklst | diff sort_blk.yaml - $
OK。
TODO