簡易なYAMLパーサ 2018夏

自分はpythonを使ってると、データファイルを作るときはついついYAMLファイル形式を選んでしまいます。

例えば レイトレーシング 2018春物体データをデータファイルに分離 てな具合に。

pythonからだと扱いが簡単で、インデントが重要なところとか、いかにもpythonと相性が良さそうですね。

そしてpythonの遅さに業を煮やして、C言語に書き換えたりしたりしますよね。

そこでC言語のYAMLのライブラリは...あれ?

C++のは充実してるのに、C言語は...あれ?

よく考えると、そらそうですよね。

YAMLで扱うリストと辞書。 各要素の型は同じである必要なくバラバラでOKです。

C言語で読み込んだとして、どうやって保持すれば良いでしょうか?

まずは、リストや辞書のC言語のライブラリ。 標準ライブラリ的なものには、おそらく無くて、 標準じゃない一長一短なライブラリは、それこそ星の数ほどある事でしょう。

うーむ。まずはリストや辞書。そこを何とかした上でのYAMLのパーサ。 どうせなら、自分で作ってみるべしですかね。

と言っても「YAML形式を完全に扱えるように」などと目論むと、挫折するのが目に見えているので、 まずは、ちょー簡単なところだけで試していきます。



まずはpythonで

pythonには当然PyYAMLモジュールが存在するので、pythonで書く必要は全くありません。

重々承知です。

いきなりC言語で書き出すよりも、 まずpythonで肩慣らししてからC言語にもっていくとしましょう。

まずは一発目。ちょー簡単仕様。「えいや」で書いてみました。

ezyaml.py

簡単なYAMLファイルを作って試しておきます。

ck1.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 ]

これで、クォートなしの文字列中で最初に登場する':'を、 辞書のキーと値を区切るコロンと解釈する事にしてみます。

v2.patch

メインの処理をちょっと変えました。

$ 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


修正V3

前回の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」に。

そして、最後に空行が追加。

修正しておきます。

v3.patch

$ 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扱いで、コーディングしてみました。

あと、空文字列のときの対応も少々。

v4.patch

$ 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。

ck3.yaml

$ 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からに固定です。

クオートで囲ってる文字列が続く場合とか、あまり深く考えず適当です。

v5.patch

$ 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。

毎度インタプリタで起動して、ブロックスタイルで保存するのも何なので...

blk.py

$ 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


python辞書比較用のツール

辞書の順不同問題で、結果の確認があまりに「はがゆい」のでツールを作りました。

変換をかければ、辞書は消えてリストになります。 元に戻すにはもう一度変換をかければ良く、先頭要素が特別なリストだけが辞書に戻ります。

diclst.py

$ 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がロードすると、リストと解釈してしまう。 なるほど。

この場合も対応は、文字列全体をシングルクオートで囲って出力するときの条件を追加ですね。

'['で始まり']'で終る場合と、辞書の'{'で始まり'}'で終る場合を追加で。

v6.patch

ついでに、ツール追加。

ioyaml.py

$ 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と解釈せねば。

v7.patch

$ 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で判定しながら コメント部分を削除してみます。

元からの空行や、処理した結果の空行も、ここで削除でよさそうですね。

v8.patch

ck1.yaml にコメントや空行を追加した ck5.aml を用意してみました。

ck5.yaml

$ 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言語版のコーディングを。

まずは、リストや辞書のC言語のライブラリ。 標準ライブラリ的なものには、おそらく無くて、 標準じゃない一長一短なライブラリは、それこそ星の数ほどある事でしょう。

などと言っておきながら、自らもその末席を汚します。

objs.[ch] でオブジェクトのリスト的なものを作ってみました。 辞書は未だですが、まぁキーと値の要素2のリストのリストにしようかと思案してます。

v9.patch

$ 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)


とりあえず動いたV10

ezyaml.py を睨みながら粛々と ezyaml.c のコーディングを進めました。

適当にでっちあげた objs.[ch] ですが、文字列のところが、なかなかすっきりしません。

ezyaml.c に追加した cp_add() cp_replace() が、charのポインタだけで何とかなりそうな予感です。

これをヒントに objs.[ch] を書き換えたいことろですが、とりあえず動く状態を1つ作っておきます。

v10.patch

$ 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: の直後...


デバッグ修正V13

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

v11.patch

$ 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

v12.patch

$ 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()からの場合を特別扱いして、 ':'が含まれてたときの扱いを変えるようにしてみます。

v13.patch

$ 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


デバッグ修正V14

では、"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 で特別扱いすべしですかね。

v14.patch

まず、修正前の状態を確認。

$ 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


デバッグ修正V16

さて、「辞書以外の文字列で文字としてのシングルクオートを含むだけの場合に限り、全体をダブルクオートで囲わずともよい問題」

そして、単なるリストの要素が文字列の場合で、文字列中に文字としてのシングルクオートが含まれる場合。 辞書の場合の':'の判定のために、このケースは全部別の種類のクオートで囲うようにしてましたが、 実は辞書以外の場合はそれも不要なのか...

と言う事だったのでとりあえず、dump_value() で辞書の右側じゃないときで、シングルクオート由来だけでダブルクオートで囲おうとしてる場合に、対策を入れるべしか。

まず、変更前の現象を確認。

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 | ./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
['', '']

この動作。

v15.patch

$ 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"'

まとめると「辞書の左のキー以外の文字列で、 クオート要因のために全体を別の種類のクオートで囲って出力 しようとしてるときは、別に全体を囲わなくても平気」対応

v16.patch

$ 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の「簡易な」処理ではハードルが高いっす。

辞書の左だけはクオートで囲って緩くしてみると

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 | ./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なのに)

v17.patch

$ 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, in 
    o = 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, in 
    o = 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側

基本的に文字列でクオートで囲む必要があるのは

さらに辞書のキー文字列のときは': 'を含むとき、':'で終るときもクオートで囲む。

(こんなシンプルで良かったのかな?)

v18.patch

$ 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, 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 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, in 
    o = 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


辞書の値側のクオート対応

辞書の右側の文字列中に':'を含む場合のクオート対応

v19.patch

$ 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;
 }
  :

v20.patch

$ 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ということで

これから何回も同じ確認手順をするだろうから、スクリプトにしておきます。

cklst.sh

$ 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[]の配列を廃止して、バッフアや文字列はポインタで管理するようにしてみました。

かなり大量に書き換えてます。

v21.patch

$ 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 の記録を残して表示する仕組みを入れて試してみます。

v22.patch

$ 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()から、呼び出し元の関数名について登録済のコールバック関数を呼び出して、 解放漏れのアドレスにある情報を、ある程度表示するようにしてみました。

v23.patch

$ 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;
}

これまた然り。

とりあえず対策。

v24.patch

$ 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

v25.patch

$ 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

v26.patch

$ 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から使うように少しだけ変更してみました。

v27.patch

$ 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