ワイヤーフレームのプログラム の更新も途絶えて久しく放置したままに、 なんとなく別のことをぼちぼち試してみようかと。
このところ本業の方で Python ばかりいじってたりするので、 自分が良く知らない「インタプリタ」を作ってみます。
遊びなので実用性は無視します。 ちゃんとしたインタプリタは世の中にごまんとあるので、 できるだけコンパクトなソースコードで、適当に動けばよしとします。
勉強がてら「よちよち」やってみます。
Python で文字列をいじるときに split() メソッドがありますよね。 引数を指定しないと、スペース、タブ、改行を区切りとして扱って、 単語のリストに分けてくれるとか。
なんだかとっても便利そう。
例えば
$ cat foo.txt int foo ( int a , int b ) { /* comment */ return a + b * 2 ; } $ $ python : >>> f = open('foo.txt', 'r') >>> s = f.read() >>> s 'int foo ( int a , int b )\n{\n /* comment */\n\n\treturn a + b * 2 ;\n}\n' >>> lst = s.split() >>> lst ['int', 'foo', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/', 'return', 'a', '+', 'b', '*', '2', ';', '}']
けっこう簡単に作れそうかも?
でもこれって実は、意図的に foo.txt にスペースを入れてるのが効いてます。
$ cat bar.txt int bar(int a, int b) { /* comment*/ return a + b * 2; } $ $ python : >>> f = open('bar.txt', 'r') >>> s = f.read() >>> s 'int bar(int a, int b)\n{\n /* comment*/\n\n\treturn a + b * 2;\n}\n' >>> s.split() ['int', 'bar(int', 'a,', 'int', 'b)', '{', '/*', 'comment*/', 'return', 'a', '+', 'b', '*', '2;', '}']
「やっぱりそう簡単じゃないよなー」ってなりますが、 まぁこれをヒントに進めてみる事にします。
単なる split() では bar.txt の 'a,' がくっついたままで、 'a' と ',' に分離できませんでした。
ならば、前処理をして bar.txt を foo.txt になるようにしてから、 split() すればどうでしょうか?
Python の文字列置換のメソッド replace() がありますよね。 例えば ',' を前後にスペースを追加した ' , ' に置換してみては?
まずは、とりあえずのお試し。 EASYなプログラムなので 'es.py' としてみます。
$ cat es.py #!/usr/bin/env python import sys def es_split(s): tbl = [ '/*', '*/', '(', ')', ';', ',' ] for s1 in tbl: d = ' ' + s1 + ' ' s = s.replace(s1, d) return s.split() if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: print('usage: {} file'.format(sys.argv[0])) else: with open(sys.argv[1], 'r') as f: s = f.read() lst = es_split(s) print(lst) sys.exit(rcode) # EOF $
es.py に実行権限を与えて bar.txt を指定して実行すると、ほれこのとおり。
$ chmod +x es.py $ ./es.py usage: ./es.py file $ ./es.py bar.txt ['int', 'bar', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/', 'return', 'a', '+', 'b', '*', '2', ';', '}'] $ ./es.py bar.txt | fold ['int', 'bar', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/' , 'return', 'a', '+', 'b', '*', '2', ';', '}']
でもこれは bar.txt の内容に絞り込んだ、とりあえず一発目のコードです。 「まだまだ」ですね。
意地悪して試練を与えてみます。
$ cat hoge.txt int hoge(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ "; return a + b * 2; } $ $ ./es.py hoge.txt | fold ['int', 'hoge', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/ ', 'char', '*s', '=', '"', 'hello', '/*', '"', ';', 'char', '*s2', '=', '"world" ', ';', 'char', '*s3', '=', '"', '*/', '"', ';', 'return', 'a', '+', 'b', '*', ' 2', ';', '}']
まず '*' をちゃんと分離したいのと、文字列の中の必要なスペースが消えてます。
tbl リストに追加だけして試してみます。
$ cp es.py es2.py $ cat es2.py-diff.txt | patch $ ./es2.py hoge.txt | fold ['int', 'hoge', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/', '*', 'comment', '*', '/', 'char', '*', 's', '=', '"', 'hello', '/', '*', '"', ';', 'char', '*', 's2', '=', '"', 'world', '"', ';', 'char', '*', 's3', '=', '"', '*', '/', '"', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
あちゃー。 引用符 '"', '"' は分離されるようになりましたが、 コメントの '/*' が '/' と '*' に分離されてしまってます。
そして、依然として文字列の中の必要なスペースは消えてます。
最後に split() でリストにするときに、 消しては困るスペースまで消えるという、根本的な問題に直面してます。
ならば、ならば、
split() にこだわり過ぎな気もしますが、こんな方針で試してみます。
どうせなら、空白文字だけに限定せず、空白文字を tbl に追加して tbl 全体をエンコードしてみます。 エンコードした状態で文字列部分を判定して、文字列と文字列以外を別のタイミングでデコードします。
さて、特殊な文字列へのエンコード。
とりあえず tbl に登録した文字列は '@' にtbl のインデックスの整数を追加した形式にしてみます。
tbl の要素数は100個くらいまで考えて、 '@{:02d}'.format( tbl.index(s1) )
などとしてみます。
差分の中身 +def enc(idx): + return '@{:02d}'.format(idx) 0以上100未満の整数 idx を与えて、'@xx' というエンコード文字列を返します。 + +def encode(s, tbl): + f = lambda t, s1: t.replace( s1, enc( tbl.index(s1) ) ) + return reduce(f, tbl, s) 文字列 s を tbl リストでエンコードした文字列を返します。 + +def decode(s, tbl, add_spc=False): + add = ' ' if add_spc else '' + f = lambda t, s1: t.replace( enc( tbl.index(s1) ), add + s1 + add) + return reduce(f, tbl, s) エンコード済の文字列 s を、tbl リストでデコードした文字列を返します。 add_spc=True を指定すると、デコード結果の単語の前後に、空白が追加されます。 + +def div_str(s, tbl): + qt = '"' + enc_qt = enc( tbl.index(qt) ) + if enc_qt not in s: + return [ [ None, s ] ] + i = s.index(enc_qt) + if i > 0: + return [ [ None, s[:i] ] ] + div_str( s[i:], tbl ) + n = len(enc_qt) + j = n + s[n:].index(enc_qt) + n if enc_qt in s[n:] else len(s) + qs = decode( s[:j], tbl ) + return [ [ 'str', qs ] ] + div_str( s[j:], tbl ) エンコード済の文字列 s について、 オリジナルが引用符 '"' で囲まれていた部分と、 それ以外の部分に切り出して、リストにして返します。 返されるリスト内の各要素は、次の形式となります。 オリジナルが'"'で囲まれていた部分 [ 'str', デコード文字列 (先頭末尾の '"' を含む) ] それ以外の部分 [ None, エンコード文字列 ] 効率は無視して、かっこ良さそうなので再帰を使ってます。 + def es_split(s): - tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"' ] - for s1 in tbl: - d = ' ' + s1 + ' ' - s = s.replace(s1, d) - return s.split() + tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n' ] + s = encode(s, tbl) + print('encode:\n{}\n'.format(s)) + + lst = div_str(s, tbl) + print('div_str:\n{}\n'.format(lst)) + + f = lambda e: decode(e[1], tbl, add_spc=True).split() if e[0] is None else [ e[1] ] + f2 = lambda t, e: t + f(e) + lst = reduce(f2, lst, []) + + return lst tbl に スペース、タブ、改行を追加して.. 全体をエンコードした結果を表示。 文字列部分デコードしたリストに変換して表示。 文字列以外の部分をデコードしつつ、前後にスペースを追加。 split() で単語にバラしたリストにして、 一本のリストにまとめなおして返してます。
では実行。
$ cp es2.py es3.py $ cat es3.py-diff.txt | patch $ ./es3.py hoge.txt | fold encode: int@08hoge@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@10 @09char@08@06s3@08=@08@07@08@01@08@07@04@10@10@09return@08a@08+@08b@08@06@082@04 @10}@10 div_str: [[None, 'int@08hoge@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10 @09char@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@ 08=@08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ "'], [None, '@04@10@10@09return@08a@08+@08b@08@06@082@04@10}@10']] ['int', 'hoge', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/ ', 'char', '*', 's', '=', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"' , ';', 'char', '*', 's3', '=', '" */ "', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
当然ですがエンコード結果にスペースはありません。 人間が見るのは厳しいですね。
最終的になんとか、コメントの '/*' はバラバラにならずに、文字列中の空白も保たれました。
でも、コメント中の空白は消えてます。まぁ、コメントなのでよしとしましょう。
ツッコミどころ満載の文字列の処理ではありますが、 もっと問題がありそうなコメント除去をしてみましょう。
単純にこれまでの処理の終段で、'/*' から '*/' までをリストから削除してみます。
+def cut_comment(lst): + sta = '/*' + end = '*/' + if sta not in lst: + return lst + i = lst.index(sta) + if i > 0: + return lst[:i] + cut_comment(lst[i:]) + j = 1 + lst[1:].index(end) + 1 if end in lst[1:] else len(lst) + return cut_comment(lst[j:]) + 単語の lst から '/*' から '*/' の範囲を削除します。 div_str(s) の 文字列 s が リスト lst に変わっただけで、 再帰も含めて処理の流れはほぼ同じです。 f2 = lambda t, e: t + f(e) lst = reduce(f2, lst, []) + print('before cut_comment:\n{}\n'.format(lst)) + lst = cut_comment(lst) + return lst es_split() の最後で cut_comment() を呼び出しを追加してます。
$ cp es3.py es4.py $ cat es4.py-diff.txt | patch $ ./es4.py hoge.txt | fold encode: int@08hoge@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@10 @09char@08@06s3@08=@08@07@08@01@08@07@04@10@10@09return@08a@08+@08b@08@06@082@04 @10}@10 div_str: [[None, 'int@08hoge@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10 @09char@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@ 08=@08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ "'], [None, '@04@10@10@09return@08a@08+@08b@08@06@082@04@10}@10']] before cut_comment: ['int', 'hoge', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/ ', 'char', '*', 's', '=', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"' , ';', 'char', '*', 's3', '=', '" */ "', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}'] ['int', 'hoge', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3 ', '=', '" */ "', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
リストから '/*', 'comment', '*/', が消えました。
では次なる試練を。
$ cat fuga.txt int fuga(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ "; /* " */ return a + b * 2; /* " */ } $ $ ./es4.py fuga.txt | fold encode: int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@10 @09char@08@06s3@08=@08@07@08@01@08@07@04@10@10@08@08@00@08@07@08@01@10@09return@ 08a@08+@08b@08@06@082@04@10@08@08@00@08@07@08@01@10}@10 div_str: [[None, 'int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10 @09char@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@ 08=@08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ "'], [None, '@04@10@10@08@08@00@08'], ['str', '" */\n\treturn a + b * 2;\n /* "'], [None, '@08@01@10}@10']] before cut_comment: ['int', 'fuga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', '/*', 'comment', '*/ ', 'char', '*', 's', '=', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"' , ';', 'char', '*', 's3', '=', '" */ "', ';', '/*', '" */\n\treturn a + b * 2;\n /* "', '*/', '}'] ['int', 'fuga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3 ', '=', '" */ "', ';', '}']
最終的に return a + b * 2;
が消えてます。
コメント除去前の単語のリストを見ると、 return の箇所は "..." の中にあり、文字列として扱われてます。
なるほど、最初に文字列の判定をしてるのでコメント中に引用符があると、 そこから文字列が始まってると思う訳か。
そうかそうか。なら文字列を切り出すよりも前に、コメント除去だ。
-def cut_comment(lst): - sta = '/*' - end = '*/' - if sta not in lst: - return lst - i = lst.index(sta) +def cut_comment(s, tbl): + sta = enc( tbl.index('/*') ) + end = enc( tbl.index('*/') ) + if sta not in s: + return '' + i = s.index(sta) if i > 0: - return lst[:i] + cut_comment(lst[i:]) - j = 1 + lst[1:].index(end) + 1 if end in lst[1:] else len(lst) - return cut_comment(lst[j:]) + return s[:i] + cut_comment( s[i:], tbl ) + sta_n = len(sta) + end_n = len(end) + j = sta_n + s[sta_n:].index(end) + end_n if end in s[sta_n:] else len(s) + return cut_comment( s[j:], tbl ) cut_comment() にはエンコード済の文字列全体を渡すことにして... コメントの開始、終了の sta, end もエンコードした文字列に。 div_str() の内容に寄せて lst を s に。 def es_split(s): tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n' ] s = encode(s, tbl) print('encode:\n{}\n'.format(s)) + s = cut_comment(s, tbl) + print('cut_comment:\n{}\n'.format(s)) + lst = div_str(s, tbl) print('div_str:\n{}\n'.format(lst)) @@ -50,9 +55,6 @@ f2 = lambda t, e: t + f(e) lst = reduce(f2, lst, []) - print('before cut_comment:\n{}\n'.format(lst)) - lst = cut_comment(lst) - return lst cut_commnet() の呼び出しを前の方に移動して、と。
$ cp es4.py es5.py $ cat es5.py-diff.txt | patch $ ./es5.py fuga.txt | fold encode: int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@10 @09char@08@06s3@08=@08@07@08@01@08@07@04@10@10@08@08@00@08@07@08@01@10@09return@ 08a@08+@08b@08@06@082@04@10@08@08@00@08@07@08@01@10}@10 cut_comment: int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@10@10@09char@08@06s@08=@08@07@ 08hello@08@08@07@04@10@10@08@08@10@09return@08a@08+@08b@08@06@082@04@10@08@08 div_str: [[None, 'int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@10@10@09char@08@06s@0 8=@08'], ['str', '" hello "'], [None, '@04@10@10@08@08@10@09return@08a@08+@08b@ 08@06@082@04@10@08@08']] ['int', 'fuga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello "', ';', 'return', 'a', '+', 'b', '*', '2', ';']
そうこれ。予め自分で仕込んだ罠が効いてます。
$ cat fuga.txt int fuga(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ "; /* " */ return a + b * 2; /* " */ } $
文字列の切り出しよりも先にコメントを除去するので、 文字列中の '/*' や '*/' にひっかり s2 の行全体が消えてます。
文字列中にある文字列としての '/*', '*/'。そして、コメントの中にあるコメントとしての引用符。この関係...
どうやら文字列の切り出しとコメントの除去は、同時にやっつけねばならんようです。
-def div_str(s, tbl):
- qt = '"'
- enc_qt = enc( tbl.index(qt) )
- if enc_qt not in s:
- return [ [ None, s ] ]
- i = s.index(enc_qt)
- if i > 0:
- return [ [ None, s[:i] ] ] + div_str( s[i:], tbl )
- n = len(enc_qt)
- j = n + s[n:].index(enc_qt) + n if enc_qt in s[n:] else len(s)
- qs = decode( s[:j], tbl )
- return [ [ 'str', qs ] ] + div_str( s[j:], tbl )
-
-def cut_comment(s, tbl):
- sta = enc( tbl.index('/*') )
- end = enc( tbl.index('*/') )
文字列とコメントを別々に処理してる関数は削除しちゃって...
+def fidx(s, lst):
+ idxs = [ s.index(t) for t in lst if t in s ]
+ return min(idxs) if len(idxs) > 0 else None
ユーティリティ関数を追加してます。
文字列リスト lst の中で、文字列 s 中に最初に見つかった位置を返します。
どれも見つからなければ None を返します。
+
+def idxs(s, sta, end):
if sta not in s:
- return ''
+ return (None, None)
i = s.index(sta)
+ i2 = i +len(sta)
+ if end not in s[i2:]:
+ return ( i, len(s) )
+ j = i2 + s[i2:].index(end)
+ j2 = j + len(end)
+ return (i, j2)
ユーティリティ関数を追加してます。
前方で削除した関数の残骸と一致する部分があって、ちょっと見づらいですが...
文字列 s の中で、文字列 sta で始まり文字列 end で終る位置を、
スライスの形式 s[a:b] だとすると、タプル(a, b) を返します。
s[a:b] には 先頭に sta 末尾に end が含まれます。
s の中に sta が無ければ (None, None) を返します。
s の中に end だけが無ければ ( xxx, len(s) ) を返します。
+
+def div_str_cut_comment(s, tbl):
+ targs = [
+ [ 'str', '"', '"' ],
+ [ 'comment', '/*', '*/' ]
+ ]
+ targs = [ [ e[0] ] + [ enc( tbl.index(s1) ) for s1 in e[1:] ] for e in targs ]
+ i = fidx( s, zip(*targs)[1] )
+ if i is None:
+ return [ [ None, s ] ]
if i > 0:
- return s[:i] + cut_comment( s[i:], tbl )
- sta_n = len(sta)
- end_n = len(end)
- j = sta_n + s[sta_n:].index(end) + end_n if end in s[sta_n:] else len(s)
- return cut_comment( s[j:], tbl )
+ return [ [ None, s[:i] ] ] + div_str_cut_comment( s[i:], tbl )
+ (k, sta, end) = (None, None, None)
+ (k, sta, end) = next( ( e for e in targs if s.startswith( e[1] ) ), (k, sta, end) )
+ (_, j) = idxs(s, sta, end)
+ r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else []
+ return r + div_str_cut_comment( s[j:], tbl )
div_str() と cut_comment() を統合して div_str_cut_comment() にしてます。
ここは差分では見づらいですね。次の内容になります。
def div_str_cut_comment(s, tbl):
targs = [
[ 'str', '"', '"' ],
[ 'comment', '/*', '*/' ]
]
targs = [ [ e[0] ] + [ enc( tbl.index(s1) ) for s1 in e[1:] ] for e in targs ]
i = fidx( s, zip(*targs)[1] )
if i is None:
return [ [ None, s ] ]
if i > 0:
return [ [ None, s[:i] ] ] + div_str_cut_comment( s[i:], tbl )
(k, sta, end) = (None, None, None)
(k, sta, end) = next( ( e for e in targs if s.startswith( e[1] ) ), (k, sta, end) )
(_, j) = idxs(s, sta, end)
r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else []
return r + div_str_cut_comment( s[j:], tbl )
targs に [ 種別, 開始文字列, 終端文字列 ] の形式で用意しておいて、
開始文字列と終端文字列はエンコードしておきます。
zip(*targs)[1]
で、開始文字列のリストにして、
fidx() で最初に見つかった s 中でのインデックスを調べます。
見つからなかった時と、先頭以外で見つかった時は、
これまで同様に再帰のお約束のパターン。
以降は文字列 s の先頭に targs のどれかの開始文字列があるはずなので、
一致する targs の要素を (k, sta, end) にとり出します。
idxs() を使って、終端文字列よりも後ろの s 中のインデックスを j に。
見つかった種別が文字列ならデコード、
種別がコメントなら返り値に含めないようにして再帰します。
def es_split(s):
tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n' ]
s = encode(s, tbl)
print('encode:\n{}\n'.format(s))
- s = cut_comment(s, tbl)
- print('cut_comment:\n{}\n'.format(s))
-
- lst = div_str(s, tbl)
+ lst = div_str_cut_comment(s, tbl)
print('div_str:\n{}\n'.format(lst))
f = lambda e: decode(e[1], tbl, add_spc=True).split() if e[0] is None else [ e[1] ]
呼び出し側も、統合した div_str_cut_comment() に。
$ cp es5.py es6.py $ cat es6.py-diff.txt | patch $ cat fuga.txt int fuga(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ "; /* " */ return a + b * 2; /* " */ } $ $ ./es6.py fuga.txt | fold encode: int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@10 @09char@08@06s3@08=@08@07@08@01@08@07@04@10@10@08@08@00@08@07@08@01@10@09return@ 08a@08+@08b@08@06@082@04@10@08@08@00@08@07@08@01@10}@10 div_str: [[None, 'int@08fuga@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09c har@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@08=@ 08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ "'], [None, '@04@10@10@08@08'], [None, '@10@09return@08a@08+@08b@08@06@082@04@10 @08@08'], [None, '@10}@10']] ['int', 'fuga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3 ', '=', '" */ "', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
これで fuga.txt の試練はクリア。
では次なる試練を。
$ cat ugaga.txt int ugaga(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* " */ } $ $ ./es6.py ugaga.txt | fold encode: int@08ugaga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@ 08@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@1 0@09char@08@06s3@08=@08@07@08@01@08\@07@08\n@07@04@10@10@08@08@00@08@07@08@01@10 @09return@08a@08+@08b@08@06@082@04@10@08@08@00@08@07@08@01@10}@10 div_str: [[None, 'int@08ugaga@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09 char@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@08= @08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ \\"'], [None, '@08\\n'], ['str', '";\n\n /* "'], [None, '@08@01@10@09return@08 a@08+@08b@08@06@082@04@10@08@08'], [None, '@10}@10']] ['int', 'ugaga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', ' =', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's 3', '=', '" */ \\"', '\\n', '";\n\n /* "', '*/', 'return', 'a', '+', 'b', '*', '2', ';', '}']
ugaga.txt の変更は s3 = " */ \" \n"
の箇所です。
文字列中に引用符を含めようと、エスケープ文字列 '\"' を追加してるのと、
文字列の末尾に改行のエスケープ文字列 '\n' を追加してます。
この追加 '\"' 箇所を文字列の終端と判定。 その直後の文字列じゃない「地」の部分に、'\n' という単語があると解釈され、 さらに以降 "; .. から /* " */ のコメント中の '"' までを文字列として解釈。 次にいきなりコメント終端 '*/' が削除されずに現れるという乱れっぷり。
そもそもソースコードの文字列の記述中に現れるエスケープ文字列は、 内部で文字列のデータとして格納するときは、しかるべき1バイトの文字に変換されてしかるべき。 (叱りすぎ?)
def es_split(s): - tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n' ] + tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n', '\\"', '\\n', '\\t' '\\\\' ] s = encode(s, tbl) print('encode:\n{}\n'.format(s)) 説明の順番をちょっと変えて、こちらから。 tbl に文字列中で使うエスケープ文字列をテキトーに追加してみつつ。 例えば、ここでの '\t' は1文字の文字列で、その1文字の値はタブコードです。 '\\t' は2文字の文字列で、1文字目が '\\' でエスケープ文字。 2文字目が 't' でアルファベットの小文字 t です。 def encode(s, tbl): f = lambda t, s1: t.replace( s1, enc( tbl.index(s1) ) ) - return reduce(f, tbl, s) + kf = lambda s1: len(s1) + stbl = sorted(tbl, key=kf, reverse=True) + return reduce(f, stbl, s) エンコードするときに、ターゲット文字列が長いものから順に置換するように変更してます。 ここまでは「たまたま」tbl の中身が、長い文字列から順に並んでました。 @@ -43,11 +45,14 @@ def div_str_cut_comment(s, tbl): (k, sta, end) = (None, None, None) (k, sta, end) = next( ( e for e in targs if s.startswith( e[1] ) ), (k, sta, end) ) (_, j) = idxs(s, sta, end) - r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else [] + + d = { '\\"' : '"', '\\n' : '\n', '\\t' : '\t', '\\\\' : '\\' } + tbl_dec_str = [ d.get(s1) if s1 in d else s1 for s1 in tbl ] + r = [ [ 'str', decode( s[:j], tbl_dec_str ) ] ] if k == 'str' else [] return r + div_str_cut_comment( s[j:], tbl ) また説明の順番をちょっと変えて、こちらから。 引用符で囲われた文字列をデコードするときは、 エスケープ文字列を、しかるべき1バイトの文字に変換しながらデコードしてます。 辞書 d に変換のルールを用意しておいて、 tbl を元にして、文字列のデコード専用のテーブル tbl_dec_str を作ってます。 文字列の decode() 呼び出しでは、tbl の変わりに tbl_dec_str を渡してます。 def decode(s, tbl, add_spc=False): add = ' ' if add_spc else '' - f = lambda t, s1: t.replace( enc( tbl.index(s1) ), add + s1 + add) - return reduce(f, tbl, s) + f = lambda t, idx: t.replace( enc(idx), add + tbl[idx] + add ) + return reduce(f, range( len(tbl) ), s) デコードは '@xx' で同じ長さなので、こちらの場合は順番はどうでもよく。 それより、文字列デコード専用の tbl を渡された時は、tbl 内に同じ単語が含まれてます。 例えば元々の改行 '\n' に対して、 文字列中のエスケープ文字列 '\\n' をデコードで変換するための '\n'。 同じ '\n' が tblの違うインデックスに存在します。 tbl から順次 s1 に単語を取り出して tbl.index(s1) としてインデックスを求めた場合、 最初に s1 が入ってるインデックスが返ります。 まずいです。後ろの方のインデックスを期待してる場面でバグります。 ここでは、インデックス idx をループ変数的に使って、 単語は tbl[idx] で参照するように修正してます。
$ cp es6.py es7.py $ cat es7.py-diff.txt | patch $ cat ugaga.txt int ugaga(int a, int b) { /* comment*/ char *s = " hello /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* " */ } $ $ ./es7.py ugaga.txt | fold encode: int@08ugaga@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@ 08@06s@08=@08@07@08hello@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07@04@1 0@09char@08@06s3@08=@08@07@08@01@08@11@08@12@07@04@10@10@08@08@00@08@07@08@01@10 @09return@08a@08+@08b@08@06@082@04@10@08@08@00@08@07@08@01@10}@10 div_str: [[None, 'int@08ugaga@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09 char@08@06s@08=@08'], ['str', '" hello /* "'], [None, '@04@10@09char@08@06s2@08= @08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', '" */ " \n"'], [None, '@04@10@10@08@08'], [None, '@10@09return@08a@08+@08b@08@06@082@ 04@10@08@08'], [None, '@10}@10']] ['int', 'ugaga', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', ' =', '" hello /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's 3', '=', '" */ " \n"', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
ugaga.txt の試練クリアです。
では次なる試練を。
$ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ $ ./es7.py guha.txt | fold encode: int@08guha@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@07@08@00@08@07@04@10@09char@08@06s2@08=@08@07world@07 @04@10@09char@08@06s3@08=@08@07@08@01@08@11@08@12@07@04@10@10@08@08@00@08@07@08@ 01@10@09return@08a@08+@08b@08@06@082@04@10@08@08@00@08@01@08@07@08@01@10}@10 div_str: [[None, 'int@08guha@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09c har@08@06s@08=@08'], ['str', '" hello "'], [None, '@08'], [None, '@08@11@08@12'] , ['str', '";\n\n /* "'], [None, '@08@01@10@09return@08a@08+@08b@08@06@082@04@1 0@08@08'], [None, '@08'], ['str', '" */\n}\n'], [None, '']] ['int', 'guha', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello "', '\\"', '\\n', '";\n\n /* "', '*/', 'return', 'a', '+', 'b', '*' , '2', ';', '" */\n}\n']
guha.txt の変更箇所は s = " hello @07 /* "
と
最後の方の /* @01 " */
のところです。
文字列の中に '@xx'、コメントの中に '@xx' を追加してるだけです。 何も悪い事はしてません。
tbl の値が
tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n', '\\"', '\\n', '\\t' '\\\\' ]
なので、@07 は '"', @01 は '*/' です。
かなりやられてます。
適当に '@xx' にエンコードしてきましたが、元々のテキストに '@xx' が現れない保証はございませんでした。
デコードするとき元々ある '@xx' と、エンコードした結果の '@xx' は区別つきません。
文字列やコメントに限らず、「地」の部分に不正な '@xx' があったとしても、 デコードした結果、その不正な問題が消えてしまえば、それはそれで問題です。
どうしたものか...
最初に '@'
を '@ '
(@ + スペース) に置換しておきましょうかね。
そして、最後に '@ '
を、元の '@'
に戻してみます。
$ cp es7.py es8.py $ cat es8.py-diff.txt | patch $ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ $ ./es8.py guha.txt | fold encode: int@08guha@02int@08a@05@08int@08b@03@10{@10@08@08@00@08comment@01@10@10@09char@0 8@06s@08=@08@07@08hello@08@@0807@08@00@08@07@04@10@09char@08@06s2@08=@08@07world @07@04@10@09char@08@06s3@08=@08@07@08@01@08@11@08@12@07@04@10@10@08@08@00@08@07@ 08@01@10@09return@08a@08+@08b@08@06@082@04@10@08@08@00@08@@0801@08@07@08@01@10}@ 10 div_str: [[None, 'int@08guha@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09c har@08@06s@08=@08'], ['str', '" hello @ 07 /* "'], [None, '@04@10@09char@08@06s2 @08=@08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', ' " */ " \n"'], [None, '@04@10@10@08@08'], [None, '@10@09return@08a@08+@08b@08@06@ 082@04@10@08@08'], [None, '@10}@10']] ['int', 'guha', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello @07 /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3', '=', '" */ " \n"', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
guha.txt の試練クリア... ってこんなので大丈夫だろうか。
単語に切り出しをしてます。
: div_str: [[None, 'int@08guha@02int@08a@05@08int@08b@03@10{@10@08@08'], [None, '@10@10@09c har@08@06s@08=@08'], ['str', '" hello @ 07 /* "'], [None, '@04@10@09char@08@06s2 @08=@08'], ['str', '"world"'], [None, '@04@10@09char@08@06s3@08=@08'], ['str', ' " */ " \n"'], [None, '@04@10@10@08@08'], [None, '@10@09return@08a@08+@08b@08@06@ 082@04@10@08@08'], [None, '@10}@10']] ['int', 'guha', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello @07 /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3', '=', '" */ " \n"', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
といった実行結果の出力ですが、
div_str: の段階の形式 ['str', '"world"']
の方が、後々便利に使えるはず。
切り出した単語をグループ分けして、[ 種別, 単語 ] をリストにした形式、 例えば次のような出力を目指してみます。
[['type', 'int'], ['name', 'guha'], ['br_s', '('], ['type', 'int'], ['name', 'a'], ['etc', ','], ['type', 'int'], ['name', 'b'], ['br_e', ')'], ['br_s', '{'], ['type', 'char'], ['op', '*'], ['name, 's'], ['op', '='], ['str', '" hello @07 /* "'], ['op', ';'], ['type', 'char'], ['op', '*'], ['name', 's2'], ['op', '='], ['str', '"world"'], ['etc', ';'], ['type, 'char'], ['op', '*'], ['name', 's3'], ['op', '='], ['str', '" */ " \n"'], ['etc', ';'], ['kwd', 'return'], ['name', 'a'], ['op', '+'], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc', ';'], ['br_e', '}']]
ですがその前段階として、 まずエンコードで使ってるテーブルを改良しておきます。
テーブルの形式を変更して同様に動くか確かめてみます。
まず差分の後ろの方から。 def es_split(s): s = s.replace('@', '@ ') - tbl = [ '/*', '*/', '(', ')', ';', ',', '*', '"', ' ', '\t', '\n', '\\"', '\\n', '\\t' '\\\\' ] + kdic = { + 'cmt_s' : [ '/*', '//' ], + 'cmt_e' : [ '*/' ], + 'qt' : [ '"', "'" ], + 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, + 'br_s' : [ '(', '[', '{' ], + 'br_e' : [ ')', ']', '}' ], + 'op' : [ '+', '-', '*' ], + 'spc' : [ ' ', '\t', '\n' ], + 'etc' : [ ';', ',' ], + } エンコードする単語のリスト tbl の形式を変えます。 元になる情報として辞書 kdic を用意します。 グループ分けは、 コメントの開始、コメントの終了、引用符、エスケープ文字列、 カッコ開き、カッコ閉じ、演算子、空白文字類、その他 辞書の値にはリスト以外に辞書も許してます。('esc'の箇所) この時エンコードのターゲットとデコードしたときの結果を、key : value とします。 + + enc = lambda ki, idx: '@{:02d}{:02d}'.format(ki, idx) + + tbl = [] + for (ki, (k, v)) in enumerate( kdic.items() ): + items = v.items() if type(v) is dict else zip(v, v) + for (idx, (s1, d1)) in enumerate(items): + tbl += [ [ k, s1, d1, enc(ki, idx) ] ] + 辞書 kdic を元に tbl を作ります。 tbl はフラットなリストで、要素は [ k, s1, d1, ec ] k : kdic の key で、グループ s1 : 単語 (エンコードターゲット文字列) d1 : 単語 (デコード結果文字列) ec : エンコード文字列 エンコード文字列の形式は '@ggxx' と5文字に変更してます。 gg : グループのインデックス xx : グループ中の単語のインデックス + kf = lambda (k, s1, d1, ec): len(s1) + tbl = sorted(tbl, key=kf, reverse=True) + print('tbl:\n{}\n'.format(tbl)) + 長い単語からエンコードしたいので、 一旦フラットなテーブルを作ってからソートしてます。 s = encode(s, tbl) print('encode:\n{}\n'.format(s)) - lst = div_str_cut_comment(s, tbl) + lst = div_str_cut_comment(s, kdic, tbl) print('div_str:\n{}\n'.format(lst)) f = lambda e: decode(e[1], tbl, add_spc=True).split() if e[0] is None else [ e[1] ] div_str_cut_coment() の引数で kdic も渡すように変更してます。 続いて差分の先頭。 -def enc(idx): - return '@{:02d}'.format(idx) - def encode(s, tbl): - f = lambda t, s1: t.replace( s1, enc( tbl.index(s1) ) ) - kf = lambda s1: len(s1) - stbl = sorted(tbl, key=kf, reverse=True) - return reduce(f, stbl, s) + f = lambda t, (k, s1, d1, ec): t.replace(s1, ec) + return reduce(f, tbl, s) def decode(s, tbl, add_spc=False): add = ' ' if add_spc else '' - f = lambda t, idx: t.replace( enc(idx), add + tbl[idx] + add ) - return reduce(f, range( len(tbl) ), s) + f = lambda t, (k, s1, d1, ec): t.replace( ec, add + d1 + add ) + return reduce(f, tbl, s) tbl の中にエンコード文字列も入れたので、 tbl を作る箇所だけでエンコード形式を知っていれば良くなりました。 enc() は廃止です。 encode(), decode() の仕様は同じですが、 tbl の中の情報が増えたので、処理は単純になりました。 -def div_str_cut_comment(s, tbl): - targs = [ - [ 'str', '"', '"' ], - [ 'comment', '/*', '*/' ] - ] - targs = [ [ e[0] ] + [ enc( tbl.index(s1) ) for s1 in e[1:] ] for e in targs ] +def div_str_cut_comment(s, kdic, tbl): + targs = [ [ 'str', s1, s1 ] for s1 in kdic.get('qt') ] + + cmt_s = kdic.get('cmt_s') + cmt_e = kdic.get('cmt_e') + f_e = lambda idx: cmt_e[idx] if idx < len(cmt_e) else '\n' + targs += [ [ 'cmt', s1, f_e(idx) ] for (idx, s1) in enumerate(cmt_s) ] + + targs = [ [ e[0] ] + [ encode(s1, tbl) for s1 in e[1:] ] for e in targs ] + i = fidx( s, zip(*targs)[1] ) if i is None: return [ [ None, s ] ] if i > 0: - return [ [ None, s[:i] ] ] + div_str_cut_comment( s[i:], tbl ) + return [ [ None, s[:i] ] ] + div_str_cut_comment( s[i:], kdic, tbl ) (k, sta, end) = (None, None, None) (k, sta, end) = next( ( e for e in targs if s.startswith( e[1] ) ), (k, sta, end) ) (_, j) = idxs(s, sta, end) - - d = { '\\"' : '"', '\\n' : '\n', '\\t' : '\t', '\\\\' : '\\' } - tbl_dec_str = [ d.get(s1) if s1 in d else s1 for s1 in tbl ] - r = [ [ 'str', decode( s[:j], tbl_dec_str ) ] ] if k == 'str' else [] - return r + div_str_cut_comment( s[j:], tbl ) + r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else [] + return r + div_str_cut_comment( s[j:], kdic, tbl ) div_str_cut_comment() の引数に kdic 追加しつつ、tbl の中身の形式も変わってます。 targs の形式は同じです。 [ 種別, 開始文字列(エンコード済), 終了文字列(エンコード済) ] のリスト kdic を使って tags を作ってます。 再帰で引数が増えたのと... あと、後半でごちゃごちゃしてた変換の箇所は、 tbl 中にエンコード前の単語とデコード後の単語を別々に持たせるようにしたので、 シンプルになりました。
$ cp es8.py es9.py $ cat es9.py-diff.txt | patch $ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ ./es9.py guha.txt | fold tbl: [['cmt_s', '/*', '/*', '@0100'], ['cmt_s', '//', '//', '@0101'], ['esc', '\\n', '\n', '@0200'], ['esc', "\\'", "'", '@0201'], ['esc', '\\t', '\t', '@0202'], ['e sc', '\\"', '"', '@0203'], ['esc', '\\\\', '\\', '@0204'], ['cmt_e', '*/', '*/', '@0300'], ['etc', ';', ';', '@0000'], ['etc', ',', ',', '@0001'], ['br_e', ')', ')', '@0400'], ['br_e', ']', ']', '@0401'], ['br_e', '}', '}', '@0402'], ['op', '+', '+', '@0500'], ['op', '-', '-', '@0501'], ['op', '*', '*', '@0502'], ['spc ', ' ', ' ', '@0600'], ['spc', '\t', '\t', '@0601'], ['spc', '\n', '\n', '@0602' ], ['br_s', '(', '(', '@0700'], ['br_s', '[', '[', '@0701'], ['br_s', '{', '{', '@0702'], ['qt', '"', '"', '@0800'], ['qt', "'", "'", '@0801']] encode: int@0600guha@0700int@0600a@0001@0600int@0600b@0400@0602@0702@0602@0600@0600@0100 @0600comment@0300@0602@0602@0601char@0600@0502s@0600=@0600@0800@0600hello@0600@@ 060007@0600@0100@0600@0800@0000@0602@0601char@0600@0502s2@0600=@0600@0800world@0 800@0000@0602@0601char@0600@0502s3@0600=@0600@0800@0600@0300@0600@0203@0600@0200 @0800@0000@0602@0602@0600@0600@0100@0600@0800@0600@0300@0602@0601return@0600a@06 00@0500@0600b@0600@0502@06002@0000@0602@0600@0600@0100@0600@@060001@0600@0800@06 00@0300@0602@0402@0602 div_str: [[None, 'int@0600guha@0700int@0600a@0001@0600int@0600b@0400@0602@0702@0602@0600@ 0600'], [None, '@0602@0602@0601char@0600@0502s@0600=@0600'], ['str', '" hello @ 07 /* "'], [None, '@0000@0602@0601char@0600@0502s2@0600=@0600'], ['str', '"world "'], [None, '@0000@0602@0601char@0600@0502s3@0600=@0600'], ['str', '" */ " \n"'] , [None, '@0000@0602@0602@0600@0600'], [None, '@0602@0601return@0600a@0600@0500@ 0600b@0600@0502@06002@0000@0602@0600@0600'], [None, '@0602@0402@0602']] ['int', 'guha', '(', 'int', 'a', ',', 'int', 'b', ')', '{', 'char', '*', 's', '= ', '" hello @07 /* "', ';', 'char', '*', 's2', '=', '"world"', ';', 'char', '*', 's3', '=', '" */ " \n"', ';', 'return', 'a', '+', 'b', '*', '2', ';', '}']
よしよし。最後の表示箇所は変化なし。
では本題のグループ分けをば。
@@ -72,19 +72,19 @@ def es_split(s): kf = lambda (k, s1, d1, ec): len(s1) tbl = sorted(tbl, key=kf, reverse=True) - print('tbl:\n{}\n'.format(tbl)) + #print('tbl:\n{}\n'.format(tbl)) s = encode(s, tbl) - print('encode:\n{}\n'.format(s)) + #print('encode:\n{}\n'.format(s)) lst = div_str_cut_comment(s, kdic, tbl) print('div_str:\n{}\n'.format(lst)) - f = lambda e: decode(e[1], tbl, add_spc=True).split() if e[0] is None else [ e[1] ] - f2 = lambda t, e: t + f(e) - lst = reduce(f2, lst, []) - - lst = [ s1.replace('@ ', '@') for s1 in lst ] + dec_split = lambda s: decode(s, tbl, add_spc=True).split() + get_k = lambda s1: next( ( k for (k, s1_, d1, ec) in tbl if s1_ == s1 ), None ) + try_split = lambda e, (k, v): [ [ get_k(s1), s1 ] for s1 in dec_split(v) ] if k is None else [ e ] + f = lambda t, e: t + try_split(e, e) + lst = reduce(f, lst, []) return lst tbl と encode 結果の表示は、もう出さずともよいでしょう。 dec_split はエンコード済の文字列 s をデコードしつつ、 結果の単語の前後に空白を追加して... split() で単語のリストに分解します。 get_k は単語 s1 のグループを返します。 try_split は [ 'str', 値 ] あるいは [ None, エンコード済文字列 ] をもらって (便宜上同じものを2つもらってます) [ None, エンコード済文字列 ] ならばデコードして単語に分解して、 できるだけ [ 種別, 単語 ] のリストにして返します。 [ 'str', 値 ] の場合はそれを要素1つのリストにして返します。 def decode(s, tbl, add_spc=False): add = ' ' if add_spc else '' f = lambda t, (k, s1, d1, ec): t.replace( ec, add + d1 + add ) - return reduce(f, tbl, s) + return reduce(f, tbl, s).replace('@ ', '@') 最後に処理してた '@' の復元は、 各デコードの時点で行なうように移動しました。
$ cp es9.py esA.py $ cat esA.py-diff.txt | patch $ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ $ ./esA.py guha.txt | fold div_str: [[None, 'int@0600guha@0700int@0600a@0001@0600int@0600b@0400@0602@0702@0602@0600@ 0600'], [None, '@0602@0602@0601char@0600@0502s@0600=@0600'], ['str', '" hello @0 7 /* "'], [None, '@0000@0602@0601char@0600@0502s2@0600=@0600'], ['str', '"world" '], [None, '@0000@0602@0601char@0600@0502s3@0600=@0600'], ['str', '" */ " \n"'], [None, '@0000@0602@0602@0600@0600'], [None, '@0602@0601return@0600a@0600@0500@0 600b@0600@0502@06002@0000@0602@0600@0600'], [None, '@0602@0402@0602']] [[None, 'int'], [None, 'guha'], ['br_s', '('], [None, 'int'], [None, 'a'], ['etc ', ','], [None, 'int'], [None, 'b'], ['br_e', ')'], ['br_s', '{'], [None, 'char' ], ['op', '*'], [None, 's'], [None, '='], ['str', '" hello @07 /* "'], ['etc', ' ;'], [None, 'char'], ['op', '*'], [None, 's2'], [None, '='], ['str', '"world"'], ['etc', ';'], [None, 'char'], ['op', '*'], [None, 's3'], [None, '='], ['str', ' " */ " \n"'], ['etc', ';'], [None, 'return'], [None, 'a'], ['op', '+'], [None, ' b'], ['op', '*'], [None, '2'], ['etc', ';'], ['br_e', '}']]
なかないい感じです。 目標に近づきつつも、'kwd', 'name', 'num' の種別が None。
では kdic に予約語を登録するなどしてみましょう。
+def is_num(s): + try: + float(s) + except ValueError: + return False + return True 文字列 s が数値か判定します。 Python なので float に変換できればよし。 + +def is_name(s): + lst = [ c for c in s ] + f1 = lambda c: c.isalpha() or c == '_' + f = lambda c: c.isalnum() or c == '_' + return len(lst) > 0 and f1( lst[0] ) and all( [ f(c) for c in lst[1:] ] ) 文字列 s が名前か判定します。 先頭はアルファベットか'_'か。 2文字目以降は数字も許します。 @@ -58,6 +71,8 @@ def es_split(s): 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], 'op' : [ '+', '-', '*' ], + 'type' : [ 'int', 'char', 'void' ], + 'kwd' : [ 'return' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';', ',' ], } kdic に 'type' と 'kwd' を追加してます。 @@ -86,6 +101,10 @@ def es_split(s): f = lambda t, e: t + try_split(e, e) lst = reduce(f, lst, []) + num_name = lambda v: 'num' if is_num(v) else ( 'name' if is_name(v) else 'None' ) + try_num_name = lambda e, (k, v): [ num_name(v), v ] if k is None else e + lst = [ try_num_name(e, e) for e in lst ] + return lst 種別が None の単語を調べて、数値か名前ならばそのように種別を更新します。
$ cp esA.py esB.py $ cat esB.py-diff.txt | patch $ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ $ ./esB.py guha.txt | fold div_str: [[None, '@0900@0300guha@0400@0900@0300a@0701@0300@0900@0300b@0100@0302@0402@0302 @0300@0300'], [None, '@0302@0302@0301@0901@0300@1002s@0300=@0300'], ['str', '" h ello @07 /* "'], [None, '@0700@0302@0301@0901@0300@1002s2@0300=@0300'], ['str', '"world"'], [None, '@0700@0302@0301@0901@0300@1002s3@0300=@0300'], ['str', '" */ " \n"'], [None, '@0700@0302@0302@0300@0300'], [None, '@0302@0301@0500@0300a@030 0@1000@0300b@0300@1002@03002@0700@0302@0300@0300'], [None, '@0302@0102@0302']] [['type', 'int'], ['name', 'guha'], ['br_s', '('], ['type', 'int'], ['name', 'a' ], ['etc', ','], ['type', 'int'], ['name', 'b'], ['br_e', ')'], ['br_s', '{'], [ 'type', 'char'], ['op', '*'], ['name', 's'], ['None', '='], ['str', '" hello @07 /* "'], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', 's2'], ['None', ' ='], ['str', '"world"'], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', ' s3'], ['None', '='], ['str', '" */ " \n"'], ['etc', ';'], ['kwd', 'return'], ['n ame', 'a'], ['op', '+'], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc', ';'], ['br_e', '}']]
guha.txt の範囲内だけですが、とりあえず単語のグループ分けの目標クリアです。
フラットなリストから、大中小のカッコを手掛かりに階層化してみます。
リスト中に現れる「カッコ開き」の要素は、次のいづれかの形式です。
[ 'bra_s', '[' ] [ 'bra_s', '{' ] [ 'bra_s', '(' ]
対応する「カッコ閉じ」までの要素をリストにして、 先頭の要素 [ 'bra_s', xxx ] の中へと append して「入れ子」にしてみます。
大中小の区別は「カッコ開き」だけあればわかるので、 「カッコ閉じ」は含めないことにしてみます。
これで、ここまで扱ってきたリスト中の各要素の形式
[ 種別, 単語 ]
という長さが2のリスト、という決めごとは崩れてしまいました。 3つめの要素以降はオプションで
[ 種別, 単語, { 配下の階層や付随する情報など... } ]
とでもなりましょうか。
@@ -60,6 +61,18 @@ def div_str_cut_comment(s, kdic, tbl): r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else [] return r + div_str_cut_comment( s[j:], kdic, tbl ) +def tree_bra(lst, kdic, sta=None): + d = dict( zip( kdic.get('br_s'), kdic.get('br_e') ) ) + dst = [] + while len(lst) > 0: + e = lst.pop(0) + if e == [ 'br_e', d.get(sta) ]: + break + if e in [ [ 'br_s', s1 ] for s1 in d.keys() ]: + e.append( tree_bra( lst, kdic, e[1] ) ) + dst.append(e) + return dst + def es_split(s): s = s.replace('@', '@ ') lst の中のカッコ開きから、カッコ閉じまで調べて、階層化します。 引数 sta は最初の呼び出しは、指定なしで None 。 再帰では '[', '{', '(' のいづれかを指定します。 @@ -105,6 +118,8 @@ def es_split(s): try_num_name = lambda e, (k, v): [ num_name(v), v ] if k is None else e lst = [ try_num_name(e, e) for e in lst ] + lst = tree_bra(lst, kdic) + return lst es_split() 中に呼び出を追加してます。 でも、es_split() という名前にそぐわない処理かも。 @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys +import yaml def is_num(s): try: @@ -115,6 +130,7 @@ if __name__ == "__main__": with open(sys.argv[1], 'r') as f: s = f.read() lst = es_split(s) - print(lst) + print('{}\n'.format(lst)) + print yaml.dump(lst) sys.exit(rcode) # EOF リストのままの表示だけでは、分かりにくいので、 yaml形式の表示も追加してます。 yamlライブラリが無い場合はインストールしてみてください。
それでは実行。
$ cp esB.py esC.py $ cat esC.py-diff.txt | patch $ cat guha.txt int guha(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; /* " */ return a + b * 2; /* @01 " */ } $ $ ./esC.py guha.txt | fold div_str: [[None, '@0900@0300guha@0400@0900@0300a@0701@0300@0900@0300b@0100@0302@0402@0302 @0300@0300'], [None, '@0302@0302@0301@0901@0300@1002s@0300=@0300'], ['str', '" h ello @07 /* "'], [None, '@0700@0302@0301@0901@0300@1002s2@0300=@0300'], ['str', '"world"'], [None, '@0700@0302@0301@0901@0300@1002s3@0300=@0300'], ['str', '" */ " \n"'], [None, '@0700@0302@0302@0300@0300'], [None, '@0302@0301@0500@0300a@030 0@1000@0300b@0300@1002@03002@0700@0302@0300@0300'], [None, '@0302@0102@0302']] [['type', 'int'], ['name', 'guha'], ['br_s', '(', [['type', 'int'], ['name', 'a' ], ['etc', ','], ['type', 'int'], ['name', 'b']]], ['br_s', '{', [['type', 'char '], ['op', '*'], ['name', 's'], ['None', '='], ['str', '" hello @07 /* "'], ['et c', ';'], ['type', 'char'], ['op', '*'], ['name', 's2'], ['None', '='], ['str', '"world"'], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', 's3'], ['None' , '='], ['str', '" */ " \n"'], ['etc', ';'], ['kwd', 'return'], ['name', 'a'], [ 'op', '+'], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc', ';']]]] - [type, int] - [name, guha] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - - br_s - '{' - - [type, char] - [op, '*'] - [name, s] - [None, '='] - [str, '" hello @07 /* "'] - [etc, ;] - [type, char] - [op, '*'] - [name, s2] - [None, '='] - [str, '"world"'] - [etc, ;] - [type, char] - [op, '*'] - [name, s3] - [None, '='] - [str, "\" */ \" \n\""] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
では次なる試練を。
$ cat hanya.txt int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int hanya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; a = sub(sub(b)); /* " */ return a + b * 2; /* @01 " */ } $ $ ./esC.py hanya.txt | fold div_str: [[None, '@0900@0300sub@0400@0900@0300v@0100@0302@0402@0302@0301@0900@0300@0500_v alue@0300=@0300@0400v@0300@1000@03001@0100@0300@1002@03002@0700@0302@0301@0500@0 300@0500_value@0700@0302@0102@0302@0302@0900@0300hanya@0400@0900@0300a@0701@0300 @0900@0300b@0100@0302@0402@0302@0300@0300'], [None, '@0302@0302@0301@0901@0300@1 002s@0300=@0300'], ['str', '" hello @07 /* "'], [None, '@0700@0302@0301@0901@030 0@1002s2@0300=@0300'], ['str', '"world"'], [None, '@0700@0302@0301@0901@0300@100 2s3@0300=@0300'], ['str', '" */ " \n"'], [None, '@0700@0302@0302@0301a@0300=@030 0sub@0400sub@0400b@0100@0100@0700@0302@0300@0300'], [None, '@0302@0301@0500@0300 a@0300@1000@0300b@0300@1002@03002@0700@0302@0300@0300'], [None, '@0302@0102@0302 ']] [['type', 'int'], ['name', 'sub'], ['br_s', '(', [['type', 'int'], ['name', 'v'] ]], ['br_s', '{', [['type', 'int'], ['kwd', 'return'], ['name', '_value'], ['Non e', '='], ['br_s', '(', [['name', 'v'], ['op', '+'], ['num', '1']]], ['op', '*'] , ['num', '2'], ['etc', ';'], ['kwd', 'return'], ['kwd', 'return'], ['name', '_v alue'], ['etc', ';']]], ['type', 'int'], ['name', 'hanya'], ['br_s', '(', [['typ e', 'int'], ['name', 'a'], ['etc', ','], ['type', 'int'], ['name', 'b']]], ['br_ s', '{', [['type', 'char'], ['op', '*'], ['name', 's'], ['None', '='], ['str', ' " hello @07 /* "'], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', 's2'], ['None', '='], ['str', '"world"'], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', 's3'], ['None', '='], ['str', '" */ " \n"'], ['etc', ';'], ['name', 'a '], ['None', '='], ['name', 'sub'], ['br_s', '(', [['name', 'sub'], ['br_s', '(' , [['name', 'b']]]]], ['etc', ';'], ['kwd', 'return'], ['name', 'a'], ['op', '+' ], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc', ';']]]] - [type, int] - [name, sub] - - br_s - ( - - [type, int] - [name, v] - - br_s - '{' - - [type, int] - [kwd, return] - [name, _value] - [None, '='] - - br_s - ( - - [name, v] - [op, +] - [num, '1'] - [op, '*'] - [num, '2'] - [etc, ;] - [kwd, return] - [kwd, return] - [name, _value] - [etc, ;] - [type, int] - [name, hanya] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - - br_s - '{' - - [type, char] - [op, '*'] - [name, s] - [None, '='] - [str, '" hello @07 /* "'] - [etc, ;] - [type, char] - [op, '*'] - [name, s2] - [None, '='] - [str, '"world"'] - [etc, ;] - [type, char] - [op, '*'] - [name, s3] - [None, '='] - [str, "\" */ \" \n\""] - [etc, ;] - [name, a] - [None, '='] - [name, sub] - - br_s - ( - - [name, sub] - - br_s - ( - - [name, b] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
関数定義 sub を追加して、関数呼び出しを追加してます。 カッコの入れ子はいけてるようですが、 よく見ると何やらダメダメです。
変数名の return_value が return と value に分離されてしまってます。
'a+_value' は a, +, _value に分離したいのですが、 'a_return_value' は a_, return, _value に分離したくありません。
kwd, type のグループは名前として有効な文字の構成になってて、 それがより長い名前の一部に含まれることがあり得ます。
うーむ。 他の名前の一部に一致するからといって、そこだけ置換でエンコードされるのも変。
でも、前後にスペースを入れずにデコードして戻すと、元通りに戻ります。 文字列のデコードでもそうしてて、既に前科があるし...
デコード時に add_spc=True で、かつ、名前として有効でない文字列へ置換の時だけ、 前後にスペースを入れる事にしてみます。
あと、'=' の箇所の種別が None 表示になってました。kdic の 'op' に追加しておきます。
@@ -21,8 +21,8 @@ def encode(s, tbl): return reduce(f, tbl, s) def decode(s, tbl, add_spc=False): - add = ' ' if add_spc else '' - f = lambda t, (k, s1, d1, ec): t.replace( ec, add + d1 + add ) + d = lambda d1: ' ' + d1 + ' ' if add_spc and not is_name(d1) else d1 + f = lambda t, (k, s1, d1, ec): t.replace( ec, d(d1) ) return reduce(f, tbl, s).replace('@ ', '@') d(d1) で通常は d1 を返しますが、 add_spc=True かつ is_name(d1) が False の時だけ ' ' + d1 + ' ' を返します。 @@ -83,7 +83,7 @@ def es_split(s): 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], - 'op' : [ '+', '-', '*' ], + 'op' : [ '+', '-', '*', '=' ], 'type' : [ 'int', 'char', 'void' ], 'kwd' : [ 'return' ], 'spc' : [ ' ', '\t', '\n' ], 演算子に'='を追加してます。
それでは実行。
$ cp esC.py esD.py $ cat esD.py-diff.txt | patch $ cat hanya.txt int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int hanya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; a = sub(sub(b)); /* " */ return a + b * 2; /* @01 " */ } $ $ ./esD.py hanya.txt | fold div_str: [[None, '@0900@0300sub@0400@0900@0300v@0100@0302@0402@0302@0301@0900@0300@0500_v alue@0300@1003@0300@0400v@0300@1000@03001@0100@0300@1002@03002@0700@0302@0301@05 00@0300@0500_value@0700@0302@0102@0302@0302@0900@0300hanya@0400@0900@0300a@0701@ 0300@0900@0300b@0100@0302@0402@0302@0300@0300'], [None, '@0302@0302@0301@0901@03 00@1002s@0300@1003@0300'], ['str', '" hello @07 /* "'], [None, '@0700@0302@0301@ 0901@0300@1002s2@0300@1003@0300'], ['str', '"world"'], [None, '@0700@0302@0301@0 901@0300@1002s3@0300@1003@0300'], ['str', '" */ " \n"'], [None, '@0700@0302@0302 @0301a@0300@1003@0300sub@0400sub@0400b@0100@0100@0700@0302@0300@0300'], [None, ' @0302@0301@0500@0300a@0300@1000@0300b@0300@1002@03002@0700@0302@0300@0300'], [No ne, '@0302@0102@0302']] [['type', 'int'], ['name', 'sub'], ['br_s', '(', [['type', 'int'], ['name', 'v'] ]], ['br_s', '{', [['type', 'int'], ['name', 'return_value'], ['op', '='], ['br_ s', '(', [['name', 'v'], ['op', '+'], ['num', '1']]], ['op', '*'], ['num', '2'], ['etc', ';'], ['kwd', 'return'], ['name', 'return_value'], ['etc', ';']]], ['ty pe', 'int'], ['name', 'hanya'], ['br_s', '(', [['type', 'int'], ['name', 'a'], [ 'etc', ','], ['type', 'int'], ['name', 'b']]], ['br_s', '{', [['type', 'char'], ['op', '*'], ['name', 's'], ['op', '='], ['str', '" hello @07 /* "'], ['etc', '; '], ['type', 'char'], ['op', '*'], ['name', 's2'], ['op', '='], ['str', '"world" '], ['etc', ';'], ['type', 'char'], ['op', '*'], ['name', 's3'], ['op', '='], [' str', '" */ " \n"'], ['etc', ';'], ['name', 'a'], ['op', '='], ['name', 'sub'], ['br_s', '(', [['name', 'sub'], ['br_s', '(', [['name', 'b']]]]], ['etc', ';'], ['kwd', 'return'], ['name', 'a'], ['op', '+'], ['name', 'b'], ['op', '*'], ['num ', '2'], ['etc', ';']]]] - [type, int] - [name, sub] - - br_s - ( - - [type, int] - [name, v] - - br_s - '{' - - [type, int] - [name, return_value] - [op, '='] - - br_s - ( - - [name, v] - [op, +] - [num, '1'] - [op, '*'] - [num, '2'] - [etc, ;] - [kwd, return] - [name, return_value] - [etc, ;] - [type, int] - [name, hanya] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - - br_s - '{' - - [type, char] - [op, '*'] - [name, s] - [op, '='] - [str, '" hello @07 /* "'] - [etc, ;] - [type, char] - [op, '*'] - [name, s2] - [op, '='] - [str, '"world"'] - [etc, ;] - [type, char] - [op, '*'] - [name, s3] - [op, '='] - [str, "\" */ \" \n\""] - [etc, ;] - [name, a] - [op, '='] - [name, sub] - - br_s - ( - - [name, sub] - - br_s - ( - - [name, b] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
hanya.txt の試練クリアです。
ここまでの実行結果で
- [type, char] - [op, '*'] - [name, s2] - [op, '='] - [str, '"world"'] - [etc, ;]
この箇所に違和感。
type 直後の '*' は、たぶん演算子じゃないですよね。 きっと type に含まれるべき情報ですよね。
例えば次の記述があったとして。
unsigned v = 1; unsigned char **u;
kdic の type に unsigned を追加して実行すると、きっとこうなるでしょう。
- [type, unsigned] - [name, v] - [op, '='] - [num, '1'] - [etc, ;] - [type, unsigned] - [type, char] - [op, '*'] - [op, '*'] - [name, u] - [etc, ;]
そうじゃなくて後半のところは、こうなって欲しいなっと。
- [type, unsigned] - [name, v] - [op, '='] - [num, '1'] - [etc, ;] - [type, unsigned, char, *, *] - [name, u] - [etc, ;]
連続する type と type に続く '*' は、 先頭の type の 中に入れ込んでしまえばスッキリしそうです。
やってみましょう。
カッコを階層化してるので、その前のフラットなリストの段階で処理を追加した方が簡単そうです。
あと、文字列を [ 'str', "..." ]
で保持してますが、
カッコの場合は [ 'br_s, '(', ... ]
にしてました。
文字列もカッコに習って [ 'str', '"', ... ]
の形式に統一しておきます。
@@ -58,9 +58,22 @@ def div_str_cut_comment(s, kdic, tbl): (k, sta, end) = (None, None, None) (k, sta, end) = next( ( e for e in targs if s.startswith( e[1] ) ), (k, sta, end) ) (_, j) = idxs(s, sta, end) - r = [ [ 'str', decode( s[:j], tbl ) ] ] if k == 'str' else [] + e_ds = lambda ds: [ 'str', ds[0], ds[1:-1] if ds[-1] == ds[0] else ds[1:] ] + r = [ e_ds( decode( s[:j], tbl ) ) ] if k == 'str' else [] return r + div_str_cut_comment( s[j:], kdic, tbl ) 文字列の切り出しとコメント除去を同時やっつけた箇所です。 文字列をデコードしたあと [ 'str', 引用符の種類, 文字列の中身 ] の形式に変更してます。 +def join_type(top, lst): + if top is None: + i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) + return lst if i is None else lst[:i] + join_type( lst[i], lst[i+1:] ) + i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' or e == [ 'op', '*' ] ), None ) + if i is None: + return [ top ] + lst + if i > 0: + return [ top ] + lst[:i] + join_type( None, lst[i:] ) + top += lst[0][1] + return join_type( top, lst[1:] ) 連続する type や type に続く '*' を、先頭の type の中に入れ込んでいく処理です。 引数の top は、最初の呼び出しでは None を指定します。 先頭の type が見つかると、それをtopとして指定して再帰させます。 @@ -111,12 +124,14 @@ def es_split(s): dec_split = lambda s: decode(s, tbl, add_spc=True).split() get_k = lambda s1: next( ( k for (k, s1_, d1, ec) in tbl if s1_ == s1 ), None ) try_split = lambda e, (k, v): [ [ get_k(s1), s1 ] for s1 in dec_split(v) ] if k is None else [ e ] - f = lambda t, e: t + try_split(e, e) + f = lambda t, e: t + try_split(e, e[:2]) lst = reduce(f, lst, []) num_name = lambda v: 'num' if is_num(v) else ( 'name' if is_name(v) else 'None' ) try_num_name = lambda e, (k, v): [ num_name(v), v ] if k is None else e - lst = [ try_num_name(e, e) for e in lst ] + lst = [ try_num_name(e, e[:2]) for e in lst ] [ 種別,単語 ] のルールは、'str' を切り出す段階で早々と [ 種別, 単語, { 配下の階層や付随する情報など... } ] になりました。 要素数 2 を期待してる箇所に修正を入れてます。 + + lst = join_type(None, lst) lst = tree_bra(lst, kdic) そして本題の join_type() の呼び出しを追加してます。
それでは実行。
$ cp esD.py esD.py $ cat esE.py-diff.txt | patch $ cat hanya.txt int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int hanya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; a = sub(sub(b)); /* " */ return a + b * 2; /* @01 " */ } $ $ ./esE.py hanya.txt | fold div_str: [[None, '@0900@0300sub@0400@0900@0300v@0100@0302@0402@0302@0301@0900@0300@0500_v alue@0300@1003@0300@0400v@0300@1000@03001@0100@0300@1002@03002@0700@0302@0301@05 00@0300@0500_value@0700@0302@0102@0302@0302@0900@0300hanya@0400@0900@0300a@0701@ 0300@0900@0300b@0100@0302@0402@0302@0300@0300'], [None, '@0302@0302@0301@0901@03 00@1002s@0300@1003@0300'], ['str', '"', ' hello @07 /* '], [None, '@0700@0302@03 01@0901@0300@1002s2@0300@1003@0300'], ['str', '"', 'world'], [None, '@0700@0302@ 0301@0901@0300@1002s3@0300@1003@0300'], ['str', '"', ' */ " \n'], [None, '@0700@ 0302@0302@0301a@0300@1003@0300sub@0400sub@0400b@0100@0100@0700@0302@0300@0300'], [None, '@0302@0301@0500@0300a@0300@1000@0300b@0300@1002@03002@0700@0302@0300@03 00'], [None, '@0302@0102@0302']] [['type', 'int'], ['name', 'sub'], ['br_s', '(', [['type', 'int'], ['name', 'v'] ]], ['br_s', '{', [['type', 'int'], ['name', 'return_value'], ['op', '='], ['br_ s', '(', [['name', 'v'], ['op', '+'], ['num', '1']]], ['op', '*'], ['num', '2'], ['etc', ';'], ['kwd', 'return'], ['name', 'return_value'], ['etc', ';']]], ['ty pe', 'int'], ['name', 'hanya'], ['br_s', '(', [['type', 'int'], ['name', 'a'], [ 'etc', ','], ['type', 'int'], ['name', 'b']]], ['br_s', '{', [['type', 'char', ' *'], ['name', 's'], ['op', '='], ['str', '"', ' hello @07 /* '], ['etc', ';'], [ 'type', 'char', '*'], ['name', 's2'], ['op', '='], ['str', '"', 'world'], ['etc' , ';'], ['type', 'char', '*'], ['name', 's3'], ['op', '='], ['str', '"', ' */ " \n'], ['etc', ';'], ['name', 'a'], ['op', '='], ['name', 'sub'], ['br_s', '(', [ ['name', 'sub'], ['br_s', '(', [['name', 'b']]]]], ['etc', ';'], ['kwd', 'return '], ['name', 'a'], ['op', '+'], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc' , ';']]]] - [type, int] - [name, sub] - - br_s - ( - - [type, int] - [name, v] - - br_s - '{' - - [type, int] - [name, return_value] - [op, '='] - - br_s - ( - - [name, v] - [op, +] - [num, '1'] - [op, '*'] - [num, '2'] - [etc, ;] - [kwd, return] - [name, return_value] - [etc, ;] - [type, int] - [name, hanya] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - - br_s - '{' - - [type, char, '*'] - [name, s] - [op, '='] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - [name, s2] - [op, '='] - [str, '"', world] - [etc, ;] - [type, char, '*'] - [name, s3] - [op, '='] - [str, '"', " */ \" \n"] - [etc, ;] - [name, a] - [op, '='] - [name, sub] - - br_s - ( - - [name, sub] - - br_s - ( - - [name, b] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
例の箇所
- [type, char, '*'] - [name, s2] - [op, '='] - [str, '"', world] - [etc, ;]
スッキリしました。
では次なる試練を。
$ cat henya.txt int henya(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int henya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } $ 関数のプロトタイプ宣言と、配列 vec[] をテキトーに追記してます。 $ ./esE.py henya.txt | fold div_str: [[None, '@0900@0300henya@0400@0900@0300a@0701@0300@0900@0300b@0100@0700@0302@030 2@0900@0300sub@0400@0900@0300v@0100@0302@0402@0302@0301@0900@0300@0500_value@030 0@1003@0300@0400v@0300@1000@03001@0100@0300@1002@03002@0700@0302@0301@0500@0300@ 0500_value@0700@0302@0102@0302@0302@0900@0300henya@0400@0900@0300a@0701@0300@090 0@0300b@0100@0302@0402@0302@0300@0300'], [None, '@0302@0302@0301@0901@0300@1002s @0300@1003@0300'], ['str', '"', ' hello @07 /* '], [None, '@0700@0302@0301@0901@ 0300@1002s2@0300@1003@0300'], ['str', '"', 'world'], [None, '@0700@0302@0301@090 1@0300@1002s3@0300@1003@0300'], ['str', '"', ' */ " \n'], [None, '@0700@0302@030 2@0301@0900@0300vec@0401100@0101@0700@0302@0301@0900@0300sub@0400@0900@0300v@010 0@0700@0302@0302@0301a@0300@1003@0300sub@0400sub@0400b@0100@0100@0700@0302@0300@ 0300'], [None, '@0302@0301vec@0401a@1000sub@0400a@0100@0101@0300@1003@03001@0700 @0302@0302@0301@0500@0300a@0300@1000@0300b@0300@1002@03002@0700@0302@0300@0300'] , [None, '@0302@0102@0302']] [['type', 'int'], ['name', 'henya'], ['br_s', '(', [['type', 'int'], ['name', 'a '], ['etc', ','], ['type', 'int'], ['name', 'b']]], ['etc', ';'], ['type', 'int' ], ['name', 'sub'], ['br_s', '(', [['type', 'int'], ['name', 'v']]], ['br_s', '{ ', [['type', 'int'], ['name', 'return_value'], ['op', '='], ['br_s', '(', [['nam e', 'v'], ['op', '+'], ['num', '1']]], ['op', '*'], ['num', '2'], ['etc', ';'], ['kwd', 'return'], ['name', 'return_value'], ['etc', ';']]], ['type', 'int'], [' name', 'henya'], ['br_s', '(', [['type', 'int'], ['name', 'a'], ['etc', ','], [' type', 'int'], ['name', 'b']]], ['br_s', '{', [['type', 'char', '*'], ['name', ' s'], ['op', '='], ['str', '"', ' hello @07 /* '], ['etc', ';'], ['type', 'char', '*'], ['name', 's2'], ['op', '='], ['str', '"', 'world'], ['etc', ';'], ['type' , 'char', '*'], ['name', 's3'], ['op', '='], ['str', '"', ' */ " \n'], ['etc', ' ;'], ['type', 'int'], ['name', 'vec'], ['br_s', '[', [['num', '100']]], ['etc', ';'], ['type', 'int'], ['name', 'sub'], ['br_s', '(', [['type', 'int'], ['name', 'v']]], ['etc', ';'], ['name', 'a'], ['op', '='], ['name', 'sub'], ['br_s', '(' , [['name', 'sub'], ['br_s', '(', [['name', 'b']]]]], ['etc', ';'], ['name', 've c'], ['br_s', '[', [['name', 'a'], ['op', '+'], ['name', 'sub'], ['br_s', '(', [ ['name', 'a']]]]], ['op', '='], ['num', '1'], ['etc', ';'], ['kwd', 'return'], [ 'name', 'a'], ['op', '+'], ['name', 'b'], ['op', '*'], ['num', '2'], ['etc', ';' ]]]] - [type, int] - [name, henya] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [etc, ;] - [type, int] - [name, sub] - - br_s - ( - - [type, int] - [name, v] - - br_s - '{' - - [type, int] - [name, return_value] - [op, '='] - - br_s - ( - - [name, v] - [op, +] - [num, '1'] - [op, '*'] - [num, '2'] - [etc, ;] - [kwd, return] - [name, return_value] - [etc, ;] - [type, int] - [name, henya] - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - - br_s - '{' - - [type, char, '*'] - [name, s] - [op, '='] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - [name, s2] - [op, '='] - [str, '"', world] - [etc, ;] - [type, char, '*'] - [name, s3] - [op, '='] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - [name, vec] - - br_s - '[' - - [num, '100'] - [etc, ;] - [type, int] - [name, sub] - - br_s - ( - - [type, int] - [name, v] - [etc, ;] - [name, a] - [op, '='] - [name, sub] - - br_s - ( - - [name, sub] - - br_s - ( - - [name, b] - [etc, ;] - [name, vec] - - br_s - '[' - - [name, a] - [op, +] - [name, sub] - - br_s - ( - - [name, a] - [op, '='] - [num, '1'] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
何が悪いってわけじゃないけど、 'name' の箇所は、変数名だったり関数名だったりします。
'name' のあとに '(' が続くと、関数のたぐい、 'name' のあとに '[' が続くと、配列のたぐいになってますよね。
このあたり、くっつけてスッキリさせてみます。
例えば
['type', xxx], ['name', yyy], ['bra_s', '(', ...], ['bra_s', '{', ...]
の並びがくると、関数定義なので
['fdef', yyy, ['bra_s', '(', ...], ['type', xxx], ['bra_s', '{', ...] ]
に変換して [ 'fdef', 名前, 引数, 返り値の型, 本体 ] の形式にまとめてみます。
@@ -86,6 +86,34 @@ def tree_bra(lst, kdic, sta=None): dst.append(e) return dst +def is_match(lst, pat): + ckf = lambda a, b, f_eq: len(a) >= len(b) and next( + ( False for (a1, b1) in zip(a, b) if not f_eq(a1, b1) ), True ) + f = lambda e, p: ckf(e, p, lambda a1, b1: a1 == b1) + return ckf(lst, pat, f) リスト lst が リスト pat で始まっていれば True を返します。 ckf は リスト a , リスト b , リストの要素を比較する関数 f_eq を受け取ります。 リスト a が リスト b 以上の長さ、かつ、 リスト b の各要素とリスト a の先頭からの要素を比較関数 f_eq で比較して、 全て一致すれば True を返します。 + +def name_bra(lst): + if len(lst) == 0: + return lst + tbl = [ + [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ], [ 'br_s', '{' ] ], + lambda type, name, (k, v, args), (k2, v2, body): + [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] ], + [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ] ], + lambda type, name, (k, v, args): [ 'fproto', name[1], [ k, v, name_bra(args), type ] ] ], + [ [ [ 'name' ], [ 'br_s', '(' ] ], + lambda name, (k, v, args): [ 'fcall', name[1], [ k, v, name_bra(args) ] ] ], + [ [ [ 'name' ], [ 'br_s', '[' ] ], + lambda name, (k, v, idx): [ 'arr', name[1], [ k, v, name_bra(idx) ] ] ], + [ [ [ 'br_s' ] ], + lambda (k, v, slst): [ k, v, name_bra(slst) ] ] + ] + for (pat, f) in tbl: + if is_match(lst, pat): + pn = len(pat) + return [ f( *lst[:pn] ) ] + name_bra(lst[pn:]) + return [ lst[0] ] + name_bra(lst[1:]) lst を受け取り、tbl に登録してあるパターンで始まっていれば、 その箇所を登録してある変換用の関数で加工して返します。 ちょっと tbl の内容が分かりづらいですね。 tbl は [ パターン, 変換用のlambda関数 ] の組が並んでいます。 例えば tbl の先頭の登録内容では、 パターンが [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ], [ 'br_s', '{' ] ] 変換用のlambda関数が lambda type, name, (k, v, args), (k2, v2, body): [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] になります。 lst の先頭部分が [ 型, 名前, '(', '{' ] のパターンで始まっていれば、 ( 型, 名前, '(', '{' ) を引数として受け取る変換関数で、 [ 'fdef', 名前(単語のみ), '('(再帰で配下のリストも処理), 型, '{'(再帰で配下のリストも処理) ] の形式を返します。 @@ -119,7 +147,7 @@ def es_split(s): #print('encode:\n{}\n'.format(s)) lst = div_str_cut_comment(s, kdic, tbl) - print('div_str:\n{}\n'.format(lst)) + #print('div_str:\n{}\n'.format(lst)) dec_split = lambda s: decode(s, tbl, add_spc=True).split() get_k = lambda s1: next( ( k for (k, s1_, d1, ec) in tbl if s1_ == s1 ), None ) もう表示せずとも良さそうなので。 @@ -135,6 +163,8 @@ def es_split(s): lst = tree_bra(lst, kdic) + lst = name_bra(lst) + return lst カッコを階層化した後に、呼び出しを追加しています。 @@ -145,7 +175,7 @@ if __name__ == "__main__": with open(sys.argv[1], 'r') as f: s = f.read() lst = es_split(s) - print('{}\n'.format(lst)) + #print('{}\n'.format(lst)) print yaml.dump(lst) sys.exit(rcode) # EOF もう表示せずとも良さそうなので。
それでは実行。
$ cp esE.py esF.py $ cat esF.py-diff.txt | patch $ cat henya.txt int henya(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int henya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } $ $ ./esF.py henya.txt - - fproto - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - [name, return_value] - [op, '='] - - br_s - ( - - [name, v] - [op, +] - [num, '1'] - [op, '*'] - [num, '2'] - [etc, ;] - [kwd, return] - [name, return_value] - [etc, ;] - - fdef - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - [name, s] - [op, '='] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - [name, s2] - [op, '='] - [str, '"', world] - [etc, ;] - [type, char, '*'] - [name, s3] - [op, '='] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - [name, a] - [op, '='] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - arr - vec - - br_s - '[' - - [name, a] - [op, +] - - fcall - sub - - br_s - ( - - [name, a] - [op, '='] - [num, '1'] - [etc, ;] - [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
関数定義、関数呼び出し、関数のプロトタイプ宣言が fdef, fcall, fproto で区別されてますね。
vec[a+sub(a)] = 1;
の箇所も
- - arr - vec - - br_s - '[' - - [name, a] - [op, +] - - fcall - sub - - br_s - ( - - [name, a] - [op, '='] - [num, '1'] - [etc, ;]
と、それらしくなってきました。
演算子をまとめてみます。 難易度が高そうです。
return a + b * 2;
の箇所の出力は
- [kwd, return] - [name, a] - [op, +] - [name, b] - [op, '*'] - [num, '2'] - [etc, ;]
になってます。
これを
- [kwd, return] - - 'op' - '+' - [name, a] - - 'op' - '*' - [name, b] - [num, '2'] - [etc, ;]
にしたいなっと。
v1, op, v2 の並びで op が二項演算子ならば、 動詞の op を前にもってきて、v1, v2 は op の中に入れ子にします。
[ 'num', '1' ], [ 'op', '+' ], [ 'num', '2' ] ならば [ 'op', '+', [ 'num', '1' ], [ 'num', '2' ] ] という具合に。
単項演算子ならば、今はとりあえず前置形だけを前提にして、
[ 'op', '-' ], [ 'name', 'value' ], ならば [ 'op', '-', [ 'name', 'value' ] ] で。
C言語の三項演算子 'a ? b : c' は、 とりあえず後回しでごめんなさい。
演算子の優先順位と結合規則の情報が必要なので、 man operator の実行結果を写経するところから始めてみます。
課題はいっぱいありそうですが、 一旦 tree_op() という関数に処理をまとめてみました。
差分の説明は、順番を変えてこちらから。 + def es_split(s): s = s.replace('@', '@ ') + ''' + Operator Associativity + -------- ------------- + () [] -> . left to right + ! ~ ++ -- - (type) * & sizeof right to left + * / % left to right + + - left to right + << >> left to right + < <= > >= left to right + == != left to right + & left to right + ^ left to right + | left to right + && left to right + || left to right + ?: right to left + = += -= etc. right to left + , left to right + ''' + + ops = [ # term, bind, lst + [ 2, '>', [ '->', '.' ] ], + [ 1, '<', [ '!', '~', '++', '--', '-', '*', '&' ] ], # dup -,*,& + [ 2, '>', [ '*', '/', '%' ] ], # dup * + [ 2, '>', [ '+', '-' ] ], # dup - + [ 2, '>', [ '<<', '>>' ] ], + [ 2, '>', [ '<', '<=', '>', '>=' ] ], + [ 2, '>', [ '==', '!=' ] ], + [ 2, '>', [ '&' ] ], # dup & + [ 2, '>', [ '^' ] ], + [ 2, '>', [ '|' ] ], + [ 2, '>', [ '&&' ] ], + [ 2, '>', [ '||' ] ], + [ 3, '<', [ '?', ':' ] ], + [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '~=', '>>=', '<<=' ] ], + # [ 2, '>', [ ',' ] ], # ... + ] まずは $ man opearator で表示される表を、コメント('''文字列''')として張り付けて、 ops というテーブルに写経してます。 テーブルの各要素は [ (単項、二項、三項の種別), (結合規則の向き), 演算子のリスト ] の形式。 man の表と同じ見た目なので、インデックスが小さいほど、優先順位は高くなります。 + + f = lambda t, (term, bind, lst): t + [ v for v in lst if v not in t ] + ops_flat = reduce(f, ops, []) + kdic の 'op' : xxx の箇所に登録するために、 ops の中にある演算子を重複しないように、 フラットなリスト ops_flat にしてます。 kdic = { 'cmt_s' : [ '/*', '//' ], 'cmt_e' : [ '*/' ], @@ -124,7 +213,7 @@ def es_split(s): 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], - 'op' : [ '+', '-', '*', '=' ], + 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], 'kwd' : [ 'return' ], 'spc' : [ ' ', '\t', '\n' ], フラットなリスト ops_flat を kdic の 'op' に登録してます。 これでショボかった登録内容が、いっきに豪勢に。 @@ -165,8 +254,14 @@ def es_split(s): lst = name_bra(lst) + lst = tree_op(lst, ops) + return lst 関数や配列のまとめをした後、 演算子のまとめ tree_op() の呼び出しを追加してます。 +def err_exit(msg, rcode=1): + print('Err {}'.format(msg)) + sys.exit(rcode) + if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: エラー処理用に追加してます。 +def ops_idx(ops, term, op): + i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) + if i is None: + err_exit("not found term={} op='{}' in ops".format(term, op)) + return i 差分の先頭に戻って、 tree_op() で使うユーティリティ関数の追加です。 引数で指定した演算子(文字列)の、ops テーブル中のインデックスを返します。 ops : 先の ops テーブルです。 term: 単項演算子なら 1, 二項演算子なら 2 を指定します。 op : 演算子(文字列)を指定します。 + +def tree_op(lst, ops): + if len(lst) == 0: + return lst + e = lst[0] + (k, v) = e[:2] + + if k == 'br_s': + e = [ k, v, tree_op( e[2], ops ) ] + elif k in [ 'fcall', 'arr' ]: + e = [ k, v, tree_op( [ e[2] ], ops )[0] ] + elif k == 'fdef': + e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] + + if k == 'op': + r = tree_op( lst[1:], ops ) + if len(r) == 0: + err_exit("not found rignt term op='{}'".format(v)) + (k2, v2) = r[0][:2] + if k2 == 'op': + i1 = ops_idx(ops, 1, v) + i2 = ops_idx( ops, len( r[0][2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] + return [ [ k, v, r[0] ] ] + r[1:] + + if k in [ 'num', 'name', 'arr', 'fcall', 'br_s' ] and len( lst[1:] ) > 0: + e1 = lst[1] + (k1, v1) = e1[:2] + if k1 == 'op': + r = tree_op( lst[2:], ops ) + if len(r) == 0: + err_exit("not found right term op='{}'".format(v1)) + (k2, v2) = r[0][:2] + if k2 == 'op': + i1 = ops_idx(ops, 2, v1) + i2 = ops_idx( ops, len( r[0][2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] + return [ [ k1, v1, e, r[0] ] ] + r[1:] + + return [ e ] + tree_op( lst[1:], ops ) これが難儀して作った本体の関数です。 再帰処理を基本としてるので、まず先頭で終端条件判定。 先頭の (種別, 単語) を取り出してみて、 カッコの類ならとりあえず再帰で配下を処理済に。 再帰、再帰できてるので、 先頭が演算子ならば、それ以前は処理済と考えて 単項演算子として扱う処理。 そうでなくて先頭が、演算子の左辺にくるような種別で、 その次が演算子ならば、二項演算子として扱う処理。 演算子の処理の中では、 リストの演算子以降の部分を再帰させてみて、 後続をとりあえず仮に処理。 処理済の後続の先頭が、演算子かどうかで分岐してます。 このあたり、処理中の演算子と、仮処理済の後続の演算子で、 優先順位を比較して、辺の並びかえでゴニョゴニョして、 かなりややこしくなってます。 最初なので色々制限つけて単純にしたつもりで、このややこしさです。
それでは実行。
$ cp esF.py esG.py $ cat esG.py-diff.txt | patch $ cat henya.txt int henya(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int henya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } $ $ ./esG.py henya.txt - - fproto - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, return_value] - - op - '*' - - br_s - ( - - - op - + - [name, v] - [num, '1'] - [num, '2'] - [etc, ;] - [kwd, return] - [name, return_value] - [etc, ;] - - fdef - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - - op - '=' - [name, s] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s2] - [str, '"', world] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s3] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - - op - '=' - [name, a] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - op - '=' - - arr - vec - - br_s - '[' - - - op - + - [name, a] - - fcall - sub - - br_s - ( - - [name, a] - [num, '1'] - [etc, ;] - [kwd, return] - - op - + - [name, a] - - op - '*' - [name, b] - [num, '2'] - [etc, ;]
henya.txt の範囲はなんとかクリアでしょうか。
「キーワード」といっても現状は1つだけなのですが。
kdic = { : 'kwd' : [ 'return' ], : }
例えば
- [kwd, return] - [name, return_value] - [etc, ;]
このような箇所も
[ 'kwd', 'return', [ 'name', 'return_value' ] ] [ 'etc', ';' ] の入れ子にして - - kwd - return - [name, return_value] - [etc, ;]
の形式にまとめてみます。
あと 関数や配列のまとめ にバグがありました。
int sub(int v); が - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] と表示されているので [ 'fproto', 'sub', [ 'br_s', '(', [ [ 'type', 'int' ], [ 'name', 'v' ] ], [ 'type', 'int' ] ] ] [ 'etc', ';' ] とまとめられてます。
これは意図と違います。 返り値の型情報の2個めの [ 'type', 'int' ] が、 引数情報の「カッコ開き」の中に入り込んでます。
正しくは [ 'fproto', 'sub', [ 'br_s', '(', [ [ 'type', 'int' ], [ 'name', 'v' ] ] ], [ 'type', 'int' ] ] [ 'etc', ';' ] こうあるべきなので、修正しておきます。
@@ -100,7 +100,7 @@ def name_bra(lst): lambda type, name, (k, v, args), (k2, v2, body): [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] ], [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ] ], - lambda type, name, (k, v, args): [ 'fproto', name[1], [ k, v, name_bra(args), type ] ] ], + lambda type, name, (k, v, args): [ 'fproto', name[1], [ k, v, name_bra(args) ], type ] ], [ [ [ 'name' ], [ 'br_s', '(' ] ], lambda name, (k, v, args): [ 'fcall', name[1], [ k, v, name_bra(args) ] ] ], [ [ [ 'name' ], [ 'br_s', '[' ] ], fproto のバグ修正です。 +def tree_op1(lst, ops, k, v): + r = tree_op( lst[1:], ops ) + if len(r) == 0: + err_exit("not found rignt term op='{}'".format(v)) + (k2, v2) = r[0][:2] + if k2 == 'op': + i1 = ops_idx(ops, 1, v) + i2 = ops_idx( ops, len( r[0][2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] + return [ [ k, v, r[0] ] ] + r[1:] + +def tree_op2(lst, ops, e, k1, v1): + r = tree_op( lst[2:], ops ) + if len(r) == 0: + err_exit("not found right term op='{}'".format(v1)) + (k2, v2) = r[0][:2] + if k2 == 'op': + i1 = ops_idx(ops, 2, v1) + i2 = ops_idx( ops, len( r[0][2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] + return [ [ k1, v1, e, r[0] ] ] + r[1:] + def tree_op(lst, ops): if len(lst) == 0: return lst @@ -134,34 +158,34 @@ def tree_op(lst, ops): e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] if k == 'op': - r = tree_op( lst[1:], ops ) - if len(r) == 0: - err_exit("not found rignt term op='{}'".format(v)) - (k2, v2) = r[0][:2] - if k2 == 'op': - i1 = ops_idx(ops, 1, v) - i2 = ops_idx( ops, len( r[0][2:] ), v2 ) - if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): - return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] - return [ [ k, v, r[0] ] ] + r[1:] + return tree_op1(lst, ops, k, v) if k in [ 'num', 'name', 'arr', 'fcall', 'br_s' ] and len( lst[1:] ) > 0: e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': - r = tree_op( lst[2:], ops ) - if len(r) == 0: - err_exit("not found right term op='{}'".format(v1)) - (k2, v2) = r[0][:2] - if k2 == 'op': - i1 = ops_idx(ops, 2, v1) - i2 = ops_idx( ops, len( r[0][2:] ), v2 ) - if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): - return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] - return [ [ k1, v1, e, r[0] ] ] + r[1:] + return tree_op2(lst, ops, e, k1, v1) return [ e ] + tree_op( lst[1:], ops ) tree_op() がややこしく見づらいので、 単項演算子の処理と二項演算子の処理を、 tree_op1(), tree_op2() にのれん分けしてみました。 +def tree_kwd(lst, kdic): + if len(lst) == 0: + return lst + e = lst[0] + step = lambda e, i: [ e ] + tree_kwd( lst[i:], kdic ) + if type(e) is not list: + return step(e, 1) + (k, v) = e[:2] + if k == 'br_s': + e = [ k, v, tree_kwd( e[2], kdic ) ] + return step(e, 1) + if k == 'kwd': + if v in [ 'return' ] and len( lst[1:] ) > 0: + e = [ k, v, lst[1] ] + return step(e, 2) + e = e[:2] + tree_kwd( e[2:], kdic ) + return step(e, 1) + こちらが、キーワードの入れ子にする処理です。 現状は 'return' 専用です。 再帰の箇所のタイプ量を減らすべく、 step(e, i) という関数を導入してみました。 e : 返り値リストの先頭にする要素を指定します。 i : 処理中の lst のインデックス i 以降で再帰させて、 返り値リストの2つめ以降の要素にします。 @@ -230,13 +254,10 @@ def es_split(s): kf = lambda (k, s1, d1, ec): len(s1) tbl = sorted(tbl, key=kf, reverse=True) - #print('tbl:\n{}\n'.format(tbl)) s = encode(s, tbl) - #print('encode:\n{}\n'.format(s)) lst = div_str_cut_comment(s, kdic, tbl) - #print('div_str:\n{}\n'.format(lst)) dec_split = lambda s: decode(s, tbl, add_spc=True).split() get_k = lambda s1: next( ( k for (k, s1_, d1, ec) in tbl if s1_ == s1 ), None ) @@ -256,6 +277,8 @@ def es_split(s): lst = tree_op(lst, ops) + lst = tree_kwd(lst, kdic) + return lst def err_exit(msg, rcode=1): @@ -270,7 +293,6 @@ if __name__ == "__main__": with open(sys.argv[1], 'r') as f: s = f.read() lst = es_split(s) - #print('{}\n'.format(lst)) print yaml.dump(lst) sys.exit(rcode) # EOF あとは追加した tree_kwd() の呼び出し追加と、 コメントアウト行の整理です。
それでは実行。
$ cp esG.py esH.py $ cat esH.py-diff.txt | patch $ cat henya.txt int henya(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int henya(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } $ $ ./esH.py henya.txt - - fproto - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, return_value] - - op - '*' - - br_s - ( - - - op - + - [name, v] - [num, '1'] - [num, '2'] - [etc, ;] - - kwd - return - [name, return_value] - [etc, ;] - - fdef - henya - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - - op - '=' - [name, s] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s2] - [str, '"', world] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s3] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - - op - '=' - [name, a] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - op - '=' - - arr - vec - - br_s - '[' - - - op - + - [name, a] - - fcall - sub - - br_s - ( - - [name, a] - [num, '1'] - [etc, ;] - - kwd - return - - op - + - [name, a] - - op - '*' - [name, b] - [num, '2'] - [etc, ;] かなり分かりにくくなってきたので $ ./esG.py henya.txt > r.old ; ./esH.py henya.txt | diff -uLb -La r.old - --- b +++ a @@ -7,7 +7,7 @@ - [etc, ','] - [type, int] - [name, b] - - [type, int] + - [type, int] - [etc, ;] - - fdef - sub @@ -32,8 +32,9 @@ - [num, '1'] - [num, '2'] - [etc, ;] - - [kwd, return] - - [name, return_value] + - - kwd + - return + - [name, return_value] - [etc, ;] - - fdef - henya @@ -78,7 +79,7 @@ - ( - - [type, int] - [name, v] - - [type, int] + - [type, int] - [etc, ;] - - op - '=' @@ -109,13 +110,14 @@ - - [name, a] - [num, '1'] - [etc, ;] - - [kwd, return] - - - op - - + - - [name, a] + - - kwd + - return - - op - - '*' - - [name, b] - - [num, '2'] + - + + - [name, a] + - - op + - '*' + - [name, b] + - [num, '2'] - [etc, ;] $
プロトタイプの返り値の型、キーワード 'return' の入れ子、クリアです。
ここまでリストをまとめあげてきましたが、 とりあえずここらで実行させてみましょう。
実行の仕組みを仕込んでみます。
+def do_expr(expr, info): + (k, v) = expr[:2] + if k == 'kwd' and v == 'return': + r = do_expr( expr[2], info ) + info.get('call_stk')[-1]['ret'] = r + return r + return 123 # !!! + +def do_blk(blk, info): + (_, _, lst) = blk # [ 'br_s', '{', lst ] + call_inf = info.get('call_stk')[-1] + for expr in lst: + do_expr(expr, info) + if 'ret' in call_inf: + return call_inf.get('ret') + +def do_fcall(name, args, info): + lst = info.get('lst') + + f = lambda (k, nm): k == 'fdef' and nm == name + fdef = next( ( e for e in lst if f( e[:2] ) ), None ) + if fdef is None: + err_exit("not found fdef '{}'".format(name)) + + (args_info, ret_type, body) = fdef[2:] + + names = [ e[1] for e in args_info if e[0] == 'name' ] + env = dict( zip(names, args) ) + + call_stk = info.get('call_stk') + call_stk.append( { 'name' : name, 'env' : env } ) + do_blk(body, info) + call_inf = call_stk.pop() + return call_inf.get('ret', None) 関数 do_expr(), do_blk(), do_fcall() を追加してます。 関数呼び出しの処理 do_fcall() から ブロック '{ ... }' を実行する do_blk() を呼び出して、 do_blk() から 式を評価する do_expr() を呼び出してます。 do_expr() は式を評価するというか、 文を実行するというか、... まだ、今後どうなるのか分からない存在です...。 do_fcall(name, args, info) name : 関数の名前を指定します。 args : 引数に与える値を、リストで指定します。 info : その他、実行のための情報をひっくるめた辞書です。 def do_blk(blk, info) blk : 関数の本体など [ 'br_s', '{', lst ] の形式 を渡します。 info : その他、実行のための情報をひっくるめた辞書です。 do_expr(expr, info) expr : 例の [ 種別, 単語, { 配下の階層や付随する情報など... } ] な形式を与えます。 info : その他、実行のための情報をひっくるめた辞書です。 @@ -294,5 +329,10 @@ if __name__ == "__main__": s = f.read() lst = es_split(s) print yaml.dump(lst) + + argv = sys.argv[1:] + args = [ len(argv), argv ] + info = { 'lst' : lst, 'call_stk' : [] } + rcode = do_fcall( 'main', args, info ) sys.exit(rcode) # EOF do_fcall() で main 関数を呼び出します。 返り値はそのままコマンドの終了コードへ。 main 関数への引数 args は、コマンドラインから $ ./esI.py xxx.txt { yyy zzz ... } などと起動するので xxx.txt 以降を渡す事にします。 main 関数に渡すので [ argc, argv ] というリストになります。 なんやかんやの辞書 info には 'lst' : lst として、ここまで生成してきたリストと、 'call_stk' : [] として、コールスタック用の空のリストを入れてます。
そういうワケで main 関数が必要になりました。 追加しておきます。
ついでにファイル名も xxx.c に変更しておきます。
それでは実行。(今回は本当に「実行」です)
$ cp esH.py esI.py $ cat esI.py-diff.txt | patch $ cat uhyo.c int uhyo(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int uhyo(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } int main(int argc, char **argv) { return uhyo(1, 2); } $ $ ./esI.py uhyo.c - - fproto - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, return_value] - - op - '*' - - br_s - ( - - - op - + - [name, v] - [num, '1'] - [num, '2'] - [etc, ;] - - kwd - return - [name, return_value] - [etc, ;] - - fdef - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - - op - '=' - [name, s] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s2] - [str, '"', world] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s3] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - - op - '=' - [name, a] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - op - '=' - - arr - vec - - br_s - '[' - - - op - + - [name, a] - - fcall - sub - - br_s - ( - - [name, a] - [num, '1'] - [etc, ;] - - kwd - return - - op - + - [name, a] - - op - '*' - [name, b] - [num, '2'] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - uhyo - - br_s - ( - - [num, '1'] - [etc, ','] - [num, '2'] - [etc, ;] $ 同様の表示が出るだけ... 実行されてるの? コマンドの終了ステータスを表示してみると $ echo $? 123 そう。 +def do_expr(expr, info): + (k, v) = expr[:2] + if k == 'kwd' and v == 'return': + r = do_expr( expr[2], info ) + info.get('call_stk')[-1]['ret'] = r + return r + return 123 # !!! この関数で返してる値が、 コールスタックの末尾の辞書に、キー 'ret' で登録されて、 do_expr() から do_fcall() に戻り、 +def do_fcall(name, args, info): + lst = info.get('lst') + + f = lambda (k, nm): k == 'fdef' and nm == name + fdef = next( ( e for e in lst if f( e[:2] ) ), None ) + if fdef is None: + err_exit("not found fdef '{}'".format(name)) + + (args_info, ret_type, body) = fdef[2:] + + names = [ e[1] for e in args_info if e[0] == 'name' ] + env = dict( zip(names, args) ) + + call_stk = info.get('call_stk') + call_stk.append( { 'name' : name, 'env' : env } ) + do_blk(body, info) + call_inf = call_stk.pop() + return call_inf.get('ret', None) コールスタックの末尾の辞書を取り出して、'ret' の値を返して、 + rcode = do_fcall( 'main', args, info ) sys.exit(rcode) # EOF 最後に終了ステータスに設定して終了してるはず。 これだけでは何も分かりませんが、 とりあえず do_expr() までは呼び出されてるようです。
uhyo.c な範囲を出来るだけ実行できるように 処理をどんどん追加してみました。
+def get_call_inf(info): + return info.get('call_stk')[-1] ユーティリティ関数追加してます。 コールスタックのカレントの情報 (末尾? 先頭?) を返します。 + +def get_env(info): + return get_call_inf(info).get('env') ユーティリティ関数追加してます。 コールスタック上にある、引数を含むローカル変数用の辞書を返します。 + +def do_set(e, val, info): + (k, v) = e[:2] + if k == 'name': + get_env(info)[v] = val + return val + if k == 'arr': + idx = e[2] + i = int( do_expr(idx, info) ) + arr = get_arr_with_chk(v, i, info) + if arr is None: + err_exit("not found arr '{}'".format(name)) + arr[i] = val + return val + + warn_no_sup('do_set', 'k', k) 代入演算子 '=' 用の処理です。 e : 代入の左辺です。 [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式で、 今のところサポートする種別は 'name' または 'arr' です。 val : 右辺の値を指定します。 info : その他、実行のための情報をひっくるめた辞書です。 + +def do_op1(v, a, info): + a = do_expr(a, info) + if v == '-': + return -a + + warn_no_sup('do_op1', 'v', v) 単項演算子用の処理です。 v : 演算子の文字列を指定します。 今のところ '-' のみ。 a : 演算の対象を指定します。 [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式です。 info : その他、実行のための情報をひっくるめた辞書です。 + +def do_op2(v, a, b, info): + b = do_expr(b, info) + if v == '=': + return do_set(a, b, info) + + a = do_expr(a, info) + if v == '+': + return a + b + if v == '-': + return a - b + if v == '*': + return a * b + if v == '/': + return a / b + + warn_no_sup('do_op2', 'v', v) 二項演算子用の処理です。 v : 演算子の文字列を指定します。 a : 演算の対象の左辺を指定します。 [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式です。 b : 演算の対象の右辺を指定します。 [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式です。 info : その他、実行のための情報をひっくるめた辞書です。 + +def do_op(v, args, info): + term = len(args) + if term == 1: + return do_op1(v, args[0], info) + if term == 2: + return do_op2(v, args[0], args[1], info) + + warn_no_sup('do_op', 'term', term) 演算子用の処理です。 v : 演算子の文字列を指定します。 args : 演算の対象をリストで指定します。 リストの要素は、 [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式です。 info : その他、実行のための情報をひっくるめた辞書です。 + +def get_arr_with_chk(name, i, info): + arr = get_env(info).get(name) + if arr is not None and i >= len(arr): + err_exit('over acc {}[{}]'.format(name, i)) + return arr name で指定した配列用のリストをコールスタックから取得します。 無ければ None を返します。 リストが存在し、かつ引数の i 以下のサイズのときは、 オーバーアクセスエラーで終了します。 + +def do_arr(name, idx, info): + i = int( do_expr(idx, info) ) + arr = get_arr_with_chk(name, i, info) + if arr is not None: + return arr[i] + get_env(info)[name] = [ None ] * i # new + return None 配列用の処理です。 name で指定した配列用のリストがコールスタック上にあれば、 idx で指定した位置の要素を返します。 無ければ、コールスタック上に、サイズ idx のリストを作成します。 + def do_expr(expr, info): + if info.get('verb') >= 2: + print('expr={}'.format(expr)) (k, v) = expr[:2] if k == 'kwd' and v == 'return': r = do_expr( expr[2], info ) - info.get('call_stk')[-1]['ret'] = r - return r - return 123 # !!! + get_call_inf(info)['ret'] = r + return None + if k == 'num': + return float(v) + if k == 'str': + return expr[2] + if k == 'name': + return get_env(info).get(v) + if k == 'br_s': + e = expr[2][0] + return do_expr(e, info) + if k == 'op': + return do_op( v, expr[2:], info ) + if k == 'arr': + return do_arr( v, expr[2], info ) + if k == 'fcall': + (_, _, args) = expr[2] + args = [ do_expr(e, info) for e in args if e != [ 'etc', ',' ] ] + return do_fcall(v, args, info) + + if k in [ 'type', 'fproto', 'etc' ]: # do nothing !!! + return None + + warn_no_sup('do_expr', 'k', k) + return None do_expr() 関数に、分岐処理を詰め込んでみました。 キーワード 'return' の処理では、 コールスタック上の辞書に 'ret' : xxx を記録しているので、 混乱しないよう、do_expr()自体は None を返すようにしました。 def do_blk(blk, info): (_, _, lst) = blk # [ 'br_s', '{', lst ] @@ -295,9 +388,12 @@ def do_blk(blk, info): for expr in lst: do_expr(expr, info) if 'ret' in call_inf: - return call_inf.get('ret') + return do_blk() もコールスタックにまかせて、値は返さないようにしました。 def do_fcall(name, args, info): + if info.get('verb') >= 1: + print('do_fcall() name={} args={}'.format(name, args)) + lst = info.get('lst') f = lambda (k, nm): k == 'fdef' and nm == name -v オプションの verbose 表示を追加しています。 @@ -306,8 +402,8 @@ def do_fcall(name, args, info): err_exit("not found fdef '{}'".format(name)) (args_info, ret_type, body) = fdef[2:] - - names = [ e[1] for e in args_info if e[0] == 'name' ] + (_, _, args_lst) = args_info + names = [ e[1] for e in args_lst if e[0] == 'name' ] env = dict( zip(names, args) ) call_stk = info.get('call_stk') do_fcall() の処理のバグ修正です。 fdef の引数情報から、引数群の名前のリストを作るところがバグってました。 @@ -316,6 +412,12 @@ def do_fcall(name, args, info): call_inf = call_stk.pop() return call_inf.get('ret', None) +def warn_no_sup(f, t, v): + warn("{}() not support {}='{}'".format(f, t, v)) + +def warn(msg): + print('Warn {}'.format(msg)) + def err_exit(msg, rcode=1): print('Err {}'.format(msg)) sys.exit(rcode) 警告表示用の関数を追加してます。 @@ -323,16 +425,24 @@ def err_exit(msg, rcode=1): if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: - print('usage: {} file'.format(sys.argv[0])) + print('usage: {} [-v -vv] file'.format(sys.argv[0])) else: - with open(sys.argv[1], 'r') as f: + verb = 0 + argv = sys.argv[:] + for (i, opt) in enumerate( [ '-v', '-vv' ] ): + if opt in argv: + argv.remove(opt) + verb = i + 1 + with open(argv[1], 'r') as f: s = f.read() lst = es_split(s) - print yaml.dump(lst) + if verb >= 1: + print yaml.dump(lst) - argv = sys.argv[1:] + argv = argv[1:] args = [ len(argv), argv ] - info = { 'lst' : lst, 'call_stk' : [] } + info = { 'lst' : lst, 'call_stk' : [], 'verb' : verb } rcode = do_fcall( 'main', args, info ) + rcode = int(rcode) sys.exit(rcode) # EOF 末尾のここの箇所は、差分では分かりづらいので。 if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: print('usage: {} [-v -vv] file'.format(sys.argv[0])) else: verb = 0 argv = sys.argv[:] for (i, opt) in enumerate( [ '-v', '-vv' ] ): if opt in argv: argv.remove(opt) verb = i + 1 with open(argv[1], 'r') as f: s = f.read() lst = es_split(s) if verb >= 1: print yaml.dump(lst) argv = argv[1:] args = [ len(argv), argv ] info = { 'lst' : lst, 'call_stk' : [], 'verb' : verb } rcode = do_fcall( 'main', args, info ) rcode = int(rcode) sys.exit(rcode) # EOF コマンド引数に -v や -vv を指定すると、 0 だった verb の値は 1 や 2 に設定されます。 es_split() 結果の lst は、 verb が 1 以上のときだけ、yaml形式で表示するようにしました。 そして、なんやかんやの辞書 info に 'verb' : verb を追加してます。 型は python の値まかせにしてて、 今のところ数値 'num' は float で扱ってます。 main 関数の返り値も、数値だと float になるので、 int に直してからコマンド終了ステータスとして設定するように 変更してます。
それでは実行。
$ cp esI.py esJ.py $ cat esJ.py-diff.txt | patch $ ./esJ.py uhyo.c $ echo $? 18 $ main 関数が 18 を返してきました。 uhyo.c の内容にほとんど意味はないですが... ちゃんとCコンパイラで実行して答え合わせしてみます。 $ gcc -o uhyo uhyo.c $ ./uhyo $ echo $? 18 $ よし一致 ! -v -vv で表示を出してみます。 $ ./esJ.py -v uhyo.c - - fproto - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, return_value] - - op - '*' - - br_s - ( - - - op - + - [name, v] - [num, '1'] - [num, '2'] - [etc, ;] - - kwd - return - [name, return_value] - [etc, ;] - - fdef - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - - op - '=' - [name, s] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s2] - [str, '"', world] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s3] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - - op - '=' - [name, a] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - op - '=' - - arr - vec - - br_s - '[' - - - op - + - [name, a] - - fcall - sub - - br_s - ( - - [name, a] - [num, '1'] - [etc, ;] - - kwd - return - - op - + - [name, a] - - op - '*' - [name, b] - [num, '2'] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - uhyo - - br_s - ( - - [num, '1'] - [etc, ','] - [num, '2'] - [etc, ;] do_fcall() name=main args=[1, ['uhyo.c']] do_fcall() name=uhyo args=[1.0, 2.0] do_fcall() name=sub args=[2.0] do_fcall() name=sub args=[6.0] do_fcall() name=sub args=[14.0] $ 関数呼び出しの回数や順は大丈夫そうです。 続いて -vv 指定で。 $ ./esJ.py -vv uhyo.c | fold - - fproto - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - [etc, ;] - - fdef - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, return_value] - - op - '*' - - br_s - ( - - - op - + - [name, v] - [num, '1'] - [num, '2'] - [etc, ;] - - kwd - return - [name, return_value] - [etc, ;] - - fdef - uhyo - - br_s - ( - - [type, int] - [name, a] - [etc, ','] - [type, int] - [name, b] - [type, int] - - br_s - '{' - - [type, char, '*'] - - op - '=' - [name, s] - [str, '"', ' hello @07 /* '] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s2] - [str, '"', world] - [etc, ;] - [type, char, '*'] - - op - '=' - [name, s3] - [str, '"', " */ \" \n"] - [etc, ;] - [type, int] - - arr - vec - - br_s - '[' - - [num, '100'] - [etc, ;] - - fproto - sub - - br_s - ( - - [type, int] - [name, v] - [type, int] - [etc, ;] - - op - '=' - [name, a] - - fcall - sub - - br_s - ( - - - fcall - sub - - br_s - ( - - [name, b] - [etc, ;] - - op - '=' - - arr - vec - - br_s - '[' - - - op - + - [name, a] - - fcall - sub - - br_s - ( - - [name, a] - [num, '1'] - [etc, ;] - - kwd - return - - op - + - [name, a] - - op - '*' - [name, b] - [num, '2'] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - uhyo - - br_s - ( - - [num, '1'] - [etc, ','] - [num, '2'] - [etc, ;] do_fcall() name=main args=[1, ['uhyo.c']] expr=['kwd', 'return', ['fcall', 'uhyo', ['br_s', '(', [['num', '1'], ['etc', ', '], ['num', '2']]]]] expr=['fcall', 'uhyo', ['br_s', '(', [['num', '1'], ['etc', ','], ['num', '2']]] ] expr=['num', '1'] expr=['num', '2'] do_fcall() name=uhyo args=[1.0, 2.0] expr=['type', 'char', '*'] expr=['op', '=', ['name', 's'], ['str', '"', ' hello @07 /* ']] expr=['str', '"', ' hello @07 /* '] expr=['etc', ';'] expr=['type', 'char', '*'] expr=['op', '=', ['name', 's2'], ['str', '"', 'world']] expr=['str', '"', 'world'] expr=['etc', ';'] expr=['type', 'char', '*'] expr=['op', '=', ['name', 's3'], ['str', '"', ' */ " \n']] expr=['str', '"', ' */ " \n'] expr=['etc', ';'] expr=['type', 'int'] expr=['arr', 'vec', ['br_s', '[', [['num', '100']]]] expr=['br_s', '[', [['num', '100']]] expr=['num', '100'] expr=['etc', ';'] expr=['fproto', 'sub', ['br_s', '(', [['type', 'int'], ['name', 'v']]], ['type', 'int']] expr=['etc', ';'] expr=['op', '=', ['name', 'a'], ['fcall', 'sub', ['br_s', '(', [['fcall', 'sub', ['br_s', '(', [['name', 'b']]]]]]]] expr=['fcall', 'sub', ['br_s', '(', [['fcall', 'sub', ['br_s', '(', [['name', 'b ']]]]]]] expr=['fcall', 'sub', ['br_s', '(', [['name', 'b']]]] expr=['name', 'b'] do_fcall() name=sub args=[2.0] expr=['type', 'int'] expr=['op', '=', ['name', 'return_value'], ['op', '*', ['br_s', '(', [['op', '+' , ['name', 'v'], ['num', '1']]]], ['num', '2']]] expr=['op', '*', ['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]], ['nu m', '2']] expr=['num', '2'] expr=['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]] expr=['op', '+', ['name', 'v'], ['num', '1']] expr=['num', '1'] expr=['name', 'v'] expr=['etc', ';'] expr=['kwd', 'return', ['name', 'return_value']] expr=['name', 'return_value'] do_fcall() name=sub args=[6.0] expr=['type', 'int'] expr=['op', '=', ['name', 'return_value'], ['op', '*', ['br_s', '(', [['op', '+' , ['name', 'v'], ['num', '1']]]], ['num', '2']]] expr=['op', '*', ['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]], ['nu m', '2']] expr=['num', '2'] expr=['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]] expr=['op', '+', ['name', 'v'], ['num', '1']] expr=['num', '1'] expr=['name', 'v'] expr=['etc', ';'] expr=['kwd', 'return', ['name', 'return_value']] expr=['name', 'return_value'] expr=['etc', ';'] expr=['op', '=', ['arr', 'vec', ['br_s', '[', [['op', '+', ['name', 'a'], ['fcal l', 'sub', ['br_s', '(', [['name', 'a']]]]]]]], ['num', '1']] expr=['num', '1'] expr=['br_s', '[', [['op', '+', ['name', 'a'], ['fcall', 'sub', ['br_s', '(', [[ 'name', 'a']]]]]]] expr=['op', '+', ['name', 'a'], ['fcall', 'sub', ['br_s', '(', [['name', 'a']]]] ] expr=['fcall', 'sub', ['br_s', '(', [['name', 'a']]]] expr=['name', 'a'] do_fcall() name=sub args=[14.0] expr=['type', 'int'] expr=['op', '=', ['name', 'return_value'], ['op', '*', ['br_s', '(', [['op', '+' , ['name', 'v'], ['num', '1']]]], ['num', '2']]] expr=['op', '*', ['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]], ['nu m', '2']] expr=['num', '2'] expr=['br_s', '(', [['op', '+', ['name', 'v'], ['num', '1']]]] expr=['op', '+', ['name', 'v'], ['num', '1']] expr=['num', '1'] expr=['name', 'v'] expr=['etc', ';'] expr=['kwd', 'return', ['name', 'return_value']] expr=['name', 'return_value'] expr=['name', 'a'] expr=['etc', ';'] expr=['kwd', 'return', ['op', '+', ['name', 'a'], ['op', '*', ['name', 'b'], ['n um', '2']]]] expr=['op', '+', ['name', 'a'], ['op', '*', ['name', 'b'], ['num', '2']]] expr=['op', '*', ['name', 'b'], ['num', '2']] expr=['num', '2'] expr=['name', 'b'] expr=['name', 'a'] $ $ echo $? 18 $
さすがに表示が大量過ぎて、確認する気が失せました。
でも 18 なので、たぶん合ってるでしょう。:-p)
不足は山ほどありますが、 まずは条件分岐くらい出来るようにしたいですよね。
$ cat if.c int main(int argc, char **argv) { int a = 0, b = 0; if (argc == 1) a = 1; else if (argc == 2) a = 2; else { a = 3; b = argc; } return a; } $
まずはキーワードに 'if', 'else' を追加と、 演算子 '==' の処理を追加して様子をながめてみます。
@@ -239,7 +239,7 @@ def es_split(s): 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], - 'kwd' : [ 'return' ], + 'kwd' : [ 'return', 'if', 'else' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';', ',' ], } ほんとうに単に追加しただけです。 @@ -324,6 +324,8 @@ def do_op2(v, a, b, info): return a * b if v == '/': return a / b + if v == '==': + return a == b warn_no_sup('do_op2', 'v', v) そして '==' の処理は python の処理系そのままで。
それではお試しの実行。
$ cp esJ.py esK.py $ cat esK.py-diff.txt | patch $ cat if.c int main(int argc, char **argv) { int a = 0, b = 0; if (argc == 1) a = 1; else if (argc == 2) a = 2; else { a = 3; b = argc; } return a; } $ $ ./esK.py -v if.c - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, a] - [num, '0'] - [etc, ','] - - op - '=' - [name, b] - [num, '0'] - [etc, ;] - [kwd, if] - - br_s - ( - - - op - == - [name, argc] - [num, '1'] - - op - '=' - [name, a] - [num, '1'] - [etc, ;] - [kwd, else] - [kwd, if] - - br_s - ( - - - op - == - [name, argc] - [num, '2'] - - op - '=' - [name, a] - [num, '2'] - [etc, ;] - [kwd, else] - - br_s - '{' - - - op - '=' - [name, a] - [num, '3'] - [etc, ;] - - op - '=' - [name, b] - [name, argc] - [etc, ;] - - kwd - return - [name, a] - [etc, ;] do_fcall() name=main args=[1, ['if.c']] Warn do_expr() not support k='kwd' Warn do_expr() not support k='kwd' Warn do_expr() not support k='kwd' Warn do_expr() not support k='kwd' $
まず警告のメッセージのピントがズレてますね。 あとで直しておくとして...
- [kwd, if] - - br_s - ( - - - op - == - [name, argc] - [num, '1'] - - op - '=' - [name, a] - [num, '1'] - [etc, ;] - [kwd, else] - [kwd, if] - - br_s - ( - - - op - == - [name, argc] - [num, '2'] - - op - '=' - [name, a] - [num, '2'] - [etc, ;] - [kwd, else] - - br_s - '{' - - - op - '=' - [name, a] - [num, '3'] - [etc, ;] - - op - '=' - [name, b] - [name, argc] - [etc, ;]
'if', 'else' をキーワードと認識しつつも並んでるだけです。
まずは キーワードのまとめ で追加した関数 tree_kwd() に処理を追加してみます。
@@ -173,6 +173,7 @@ def tree_kwd(lst, kdic): return lst e = lst[0] step = lambda e, i: [ e ] + tree_kwd( lst[i:], kdic ) + top_is = lambda lst, k, v: len(lst) > 0 and lst[0][:2] == [ k, v ] if type(e) is not list: return step(e, 1) (k, v) = e[:2] top_is(lst, k, v) を追加しています。 lst の先頭の要素が存在して [ k, v ] なら True を返します。 @@ -183,6 +184,19 @@ def tree_kwd(lst, kdic): if v in [ 'return' ] and len( lst[1:] ) > 0: e = [ k, v, lst[1] ] return step(e, 2) + if v == 'else' and len( lst[1:] ) > 0: + r = tree_kwd( lst[1:], kdic ) + return [ [ k, v, r[0] ] ] + r[1:] + if v == 'if' and len( lst[2:] ) > 0: + if lst[1][:2] != [ 'br_s', '(' ]: + err_exit("not found '(' after 'if'") + r = tree_kwd( lst[2:], kdic ) + e = [ k, v, lst[1], r.pop(0) ] + if top_is(r, 'etc', ';'): + r.pop(0) + if top_is(r, 'kwd', 'else'): + e += [ r.pop(0)[2] ] + return [ e ] + r e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) 先に 'else' から。 後続の再帰結果リスト r を先に求めておいて、 その先頭を 'else' の入れ子にします。 次に 'if'。 '(' の条件部分を 'if' の入れ子にしつつ、 条件部分の後続を再帰で r に。 r の先頭は True 処理として入れ子に。 r のつづきが ';' ならスキップ さらにつづきが 'else' なら False 処理をとってきて入れ子に。 見直してみると else が無くて if () のあとが '{' ブロックじゃない場合、 末尾の ';' が消えてしまいますね。 まぁ、そんなに悪さしなさそうなので放置。 @@ -381,7 +395,7 @@ def do_expr(expr, info): if k in [ 'type', 'fproto', 'etc' ]: # do nothing !!! return None - warn_no_sup('do_expr', 'k', k) + warn_no_sup('do_expr', '[k, v]', [k, v]) return None do_expr() の警告表示の修正です。
それではお試しの実行。
$ cp esK.py esL.py $ cat esL.py-diff.txt | patch $ cat if.c int main(int argc, char **argv) { int a = 0, b = 0; if (argc == 1) a = 1; else if (argc == 2) a = 2; else { a = 3; b = argc; } return a; } $ $ ./esL.py -v if .c - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, a] - [num, '0'] - [etc, ','] - - op - '=' - [name, b] - [num, '0'] - [etc, ;] - - kwd - if - - br_s - ( - - - op - == - [name, argc] - [num, '1'] - - op - '=' - [name, a] - [num, '1'] - - kwd - if - - br_s - ( - - - op - == - [name, argc] - [num, '2'] - - op - '=' - [name, a] - [num, '2'] - - br_s - '{' - - - op - '=' - [name, a] - [num, '3'] - [etc, ;] - - op - '=' - [name, b] - [name, argc] - [etc, ;] - - kwd - return - [name, a] - [etc, ;] do_fcall() name=main args=[1, ['if.c']] Warn do_expr() not support [k, v]='['kwd', 'if']' $
それらしくまとまってるようです。 警告表示もとりあえずこれでよし。
それでは、関数 do_expr() の 'kwd' に処理を追加してみます。
@@ -370,10 +370,17 @@ def do_expr(expr, info): if info.get('verb') >= 2: print('expr={}'.format(expr)) (k, v) = expr[:2] - if k == 'kwd' and v == 'return': - r = do_expr( expr[2], info ) - get_call_inf(info)['ret'] = r - return None + if k == 'kwd': + if v == 'return': + r = do_expr( expr[2], info ) + get_call_inf(info)['ret'] = r + return None + if v == 'if': + if do_expr( expr[2], info ): + do_expr( expr[3], info ) + elif len(expr) > 4: + do_expr( expr[4], info ) + return None if k == 'num': return float(v) if k == 'str': 'return' のネストをずらしつつ...、 むしろこちらの if の処理は単純です。
それでは実行。
$ cp esL.py esM.py $ cat esM.py-diff.txt | patch $ cat if.c int main(int argc, char **argv) { int a = 0, b = 0; if (argc == 1) a = 1; else if (argc == 2) a = 2; else { a = 3; b = argc; } return a; } $ これまたあまり意味ないコードですが。 $ ./esM.py if.c ; echo $? 1 $ ./esM.py if.c a ; echo $? 2 $ ./esM.py if.c a b ; echo $? 3 $ ./esM.py if.c a b c ; echo $? 3
確かに引数の個数で分岐できてる様子です。
つづいてはループくらい出来るようにしたいですよね。
$ cat sum.c int main(int argc, char **argv) { int s = 0; int i = 1; while (i <= 10) { s = s + i; i = i + 1; } return s; } $
1 から 10 までの足し算で試してみます。
@@ -197,6 +197,12 @@ def tree_kwd(lst, kdic): if top_is(r, 'kwd', 'else'): e += [ r.pop(0)[2] ] return [ e ] + r + if v == 'while' and len( lst[2:] ) > 0: + if lst[1][:2] != [ 'br_s', '(' ]: + err_exit("not found '(' after 'while'") + r = tree_kwd( lst[2:], kdic ) + e = [ k, v, lst[1], r.pop(0) ] + return [ e ] + r e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) tree_kwd() 関数に、 キーワード while をみつけたら入れ子にする処理の追加です。 @@ -253,7 +259,7 @@ def es_split(s): 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], - 'kwd' : [ 'return', 'if', 'else' ], + 'kwd' : [ 'return', 'if', 'else', 'while' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';', ',' ], } kdic の 'kwd' へ 'while' 追加です。 @@ -340,6 +346,16 @@ def do_op2(v, a, b, info): return a / b if v == '==': return a == b + if v == '!=': + return a != b + if v == '<=': + return a <= b + if v == '<': + return a < b + if v == '>=': + return a >= b + if v == '>': + return a > b warn_no_sup('do_op2', 'v', v) 比較系の演算子の処理も追加しておきます。 @@ -366,21 +382,30 @@ def do_arr(name, idx, info): get_env(info)[name] = [ None ] * i # new return None +def do_kwd(v, expr, info): + if v == 'return': + r = do_expr( expr[2], info ) + get_call_inf(info)['ret'] = r + return None + if v == 'if': + if do_expr( expr[2], info ): + do_expr( expr[3], info ) + elif len(expr) > 4: + do_expr( expr[4], info ) + return None + if v == 'while': + while do_expr( expr[2], info ): + do_expr( expr[3], info ) + return None + + warn_no_sup('do_kwd', 'v', v) + def do_expr(expr, info): if info.get('verb') >= 2: print('expr={}'.format(expr)) (k, v) = expr[:2] if k == 'kwd': - if v == 'return': - r = do_expr( expr[2], info ) - get_call_inf(info)['ret'] = r - return None - if v == 'if': - if do_expr( expr[2], info ): - do_expr( expr[3], info ) - elif len(expr) > 4: - do_expr( expr[4], info ) - return None + return do_kwd(v, expr, info) if k == 'num': return float(v) if k == 'str': do_expr() から do_kwd() へ、のれん分けです。 'while' 処理の追加分は2行だけでシンプルです。 @@ -388,8 +413,12 @@ def do_expr(expr, info): if k == 'name': return get_env(info).get(v) if k == 'br_s': - e = expr[2][0] - return do_expr(e, info) + if v == '(': + e = expr[2][0] + return do_expr(e, info) + if v == '{': + do_blk(expr, info) + return None if k == 'op': return do_op( v, expr[2:], info ) if k == 'arr': do_expr() で、ここがバグってました。 '(' のときは式の評価ですが、 '{' のときはブロックを実行せねばなりませんでした。 なので if.c も、 さいごの else { ... } の中の b = argc が実行できてなかったようです。 修正しておきます。
それでは実行。
$ cp esM.py esN.py $ cat esN.py-diff.txt $ cat sum.c int main(int argc, char **argv) { int s = 0; int i = 1; while (i <= 10) { s = s + i; i = i + 1; } return s; } $ $ ./esN.py sum.c $ echo $? 55 $ 1 から 10 までの合計は 55 で合ってます。 ちなみに以下は -vv 指定ありの場合。 同じパターンの do_expr() が繰り返されていて、確かにループしてる様子です。 $ ./esN.py -vv sum.c | fold - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, s] - [num, '0'] - [etc, ;] - [type, int] - - op - '=' - [name, i] - [num, '1'] - [etc, ;] - - kwd - while - - br_s - ( - - - op - <= - [name, i] - [num, '10'] - - br_s - '{' - - - op - '=' - [name, s] - - op - + - [name, s] - [name, i] - [etc, ;] - - op - '=' - [name, i] - - op - + - [name, i] - [num, '1'] - [etc, ;] - - kwd - return - [name, s] - [etc, ;] do_fcall() name=main args=[1, ['sum.c']] expr=['type', 'int'] expr=['op', '=', ['name', 's'], ['num', '0']] expr=['num', '0'] expr=['etc', ';'] expr=['type', 'int'] expr=['op', '=', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['etc', ';'] expr=['kwd', 'while', ['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]] ], ['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['n um', '1']]], ['etc', ';']]]] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['br_s', '{', [['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name ', 'i']]], ['etc', ';'], ['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], [ 'num', '1']]], ['etc', ';']]] expr=['op', '=', ['name', 's'], ['op', '+', ['name', 's'], ['name', 'i']]] expr=['op', '+', ['name', 's'], ['name', 'i']] expr=['name', 'i'] expr=['name', 's'] expr=['etc', ';'] expr=['op', '=', ['name', 'i'], ['op', '+', ['name', 'i'], ['num', '1']]] expr=['op', '+', ['name', 'i'], ['num', '1']] expr=['num', '1'] expr=['name', 'i'] expr=['etc', ';'] expr=['br_s', '(', [['op', '<=', ['name', 'i'], ['num', '10']]]] expr=['op', '<=', ['name', 'i'], ['num', '10']] expr=['num', '10'] expr=['name', 'i'] expr=['kwd', 'return', ['name', 's']] expr=['name', 's'] $
ちょっとはまともなソースの実行を試せるようにしてみます。
例えば
#include <stdio.h> #include <stdlib.h> void show_str(char *s) { printf("%s", s); } void show_int(int v) { printf("%d", v); } int sum(int argc, char **argv, int i) { if (i >= argc) return 0; return atoi( argv[i] ) + sum(argc, argv, i+1); } int main(int argc, char **argv) { int v = sum(argc, argv, 1); show_str("sum="); show_int(v); show_str("\n"); return 0; }
引数を atoi() で整数にして足し算して合計を表示します。
関数 sum() では再帰を使ってます。 結果はコマンドの終了ステータスに頼らず、ちゃんと表示してます。
コンパイラで実行してみると
$ gcc -o sum2 sum2.c $ ./sum2 sum=0 $ ./sum2 1 1 sum=2 $ ./sum2 1 2 3 sum=6 $ ./sum2 123 456 sum=579 $
とりあえずお試し。
$ ./esN.py sum2.c Err not found term=1 op='<' in ops $
先頭の include 行の '<' を演算子と思ってる時点でアウトです。
プリプロセッサの処理まで扱うとなるとマクロの闇が深そうなので...。 '#' から行末まではコメント扱いで無視することにします。
ちなみに '//' から行末までのコメントは C じゃなくて C++ 由来のものですが、 たいがいの環境で '//' は使えるようです。
'//' 同様に '#' を登録します。
@@ -251,7 +251,7 @@ def es_split(s): ops_flat = reduce(f, ops, []) kdic = { - 'cmt_s' : [ '/*', '//' ], + 'cmt_s' : [ '/*', '//', '#' ], 'cmt_e' : [ '*/' ], 'qt' : [ '"', "'" ], 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, cmt_s が コメントの開始文字列です。 cmt_e が コメントの終端文字列です。 開始文字列と同じインデックスに終端文字列が無ければ、 終端は改行までとして扱います。
それではお試し。
$ cp esN.py esO.py $ cat esO.py-diff.txt | patch $ ./esO.py sum2.c Err not found fdef 'printf' $
まぁそうなるでしょうな。
ちなみにコマンド引数を与えてみると
$ ./esO.py sum2.c 123 Warn do_expr() not support [k, v]='['br_s', '[']' Traceback (most recent call last): File "./esO.py", line 497, inrcode = do_fcall( 'main', args, info ) File "./esO.py", line 463, in do_fcall do_blk(body, info) File "./esO.py", line 441, in do_blk do_expr(expr, info) File "./esO.py", line 423, in do_expr return do_op( v, expr[2:], info ) File "./esO.py", line 367, in do_op return do_op2(v, args[0], args[1], info) File "./esO.py", line 334, in do_op2 b = do_expr(b, info) File "./esO.py", line 429, in do_expr return do_fcall(v, args, info) File "./esO.py", line 463, in do_fcall do_blk(body, info) File "./esO.py", line 441, in do_blk do_expr(expr, info) File "./esO.py", line 408, in do_expr return do_kwd(v, expr, info) File "./esO.py", line 387, in do_kwd r = do_expr( expr[2], info ) File "./esO.py", line 423, in do_expr return do_op( v, expr[2:], info ) File "./esO.py", line 367, in do_op return do_op2(v, args[0], args[1], info) File "./esO.py", line 338, in do_op2 a = do_expr(a, info) File "./esO.py", line 428, in do_expr args = [ do_expr(e, info) for e in args if e != [ 'etc', ',' ] ] File "./esO.py", line 425, in do_expr return do_arr( v, expr[2], info ) File "./esO.py", line 378, in do_arr i = int( do_expr(idx, info) ) TypeError: int() argument must be a string or a number, not 'NoneType' $
なにやら色々メッセージが。
先頭のメッセージによりますと「do_expr() で '[' が来たけど処理が無い」とな。
ということは配列関係なので sum2.c の argv[i] のあたり。
def do_expr(expr, info): : if k == 'arr': return do_arr( v, expr[2], info ) から def do_arr(name, idx, info): i = int( do_expr(idx, info) ) arr = get_arr_with_chk(name, i, info) if arr is not None: return arr[i] get_env(info)[name] = [ None ] * i # new return None ああ、expr[2] からの idx が [ 'br_s', '[', ... ] 形式。 また do_expr() 呼び出しで def do_expr(expr, info): : if k == 'br_s': if v == '(': e = expr[2][0] return do_expr(e, info) if v == '{': do_blk(expr, info) return None if k == 'op': : 確かに、'[' の処理が無いです。
[ ... ] の中を評価したいのですが、 結局 ( ... ) の中を評価するときと同じ処理になります。
それはそれで修正を入れるとして、さて。 printf() が無いという根本的な問題。
ここで「ちゃぶ台返し」的な対応をば。
関数呼び出しで関数定義が見つからなければ、 python の処理系で同じ名前の関数を探してみる事にしましょう。
もし見つかれば、ここは一つそれを使わせて頂くということで。 それでも無ければエラーということで。
@@ -3,6 +3,12 @@ import sys import yaml +def printf(s, v): + sys.stdout.write(s % v) + +def atoi(s): + return eval(s) + def is_num(s): try: float(s) python 側に printf() と atoi() を追加してみました。 printf() は引数2つの仕様に固定。 atoi() も反則な eval() で。 なんて卑怯な。 @@ -413,7 +419,7 @@ def do_expr(expr, info): if k == 'name': return get_env(info).get(v) if k == 'br_s': - if v == '(': + if v in [ '(', '[' ]: # '[' for do_arr() e = expr[2][0] return do_expr(e, info) if v == '{': 配列アクセスのバグ修正です。 @@ -442,6 +448,13 @@ def do_blk(blk, info): if 'ret' in call_inf: return +def get_sys_func(name): + try: + func = eval(name) + except NameError: + return None + return func + def do_fcall(name, args, info): if info.get('verb') >= 1: print('do_fcall() name={} args={}'.format(name, args)) 関数 get_sys_func(name) を追加しています。 反則の eval() で関数オブジェクトが返ることを期待してます。 無ければ None を返します。 @@ -451,6 +464,9 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: + func = get_sys_func(name) + if func: + return func(*args) err_exit("not found fdef '{}'".format(name)) (args_info, ret_type, body) = fdef[2:] リスト中に関数定義が見つからなければ、 get_sys_func() で python 側の環境を探します。 見つかれば引数のリストを展開して python の関数呼び出し。 それでも見つからなければエラーです。
それでは実行。
$ cp esO.py esP.py $ cat esP.py-diff.txt | patch $ ./esP.py sum2.c sum=0 $ ./esP.py sum2.c 123 sum=123 $ ./esP.py sum2.c 1 2 3 4 5 6 7 8 9 10 sum=55 $
とりあえず誤魔化した感がありますが、動きました。
やはり寝ざめが悪いです。
@@ -3,6 +3,12 @@ import sys import yaml +def printf(s, v): + sys.stdout.write(s % v) + +def atoi(s): + return eval(s) + def is_num(s): try: float(s)
インタプリタの python 側にこの追加は、いただけません。
このアイデアでどうでしょうか。
$ cat sum3.c #include <stdio.h> #include <stdlib.h> void show_str(char *s) { printf("%s", s); } void show_int(int v) { printf("%d", v); } /*-->| void atoi(char *s) { return eval(s) } |<--*/ int sum(int argc, char **argv, int i) { if (i >= argc) return 0; return atoi( argv[i] ) + sum(argc, argv, i+1); } int main(int argc, char **argv) { int v = sum(argc, argv, 1); show_str("sum="); show_int(v); show_str("\n"); return 0; } $ $ diff -u -L before -L after sum2.c sum3.c --- before +++ after @@ -11,6 +11,13 @@ printf("%d", v); } +/*-->| +void atoi(char *s) +{ + return eval(s) +} +|<--*/ + int sum(int argc, char **argv, int i) { if (i >= argc) return 0; $
ぱっと見てコメントの追加です。
$ gcc -o sum3 sum3.c $ ./sum3 1 2 3 sum=6 $
Cコンパイラからするとコメントが追加されただけで、以前と変わりません。
ここでひとつウルトラCを。
@@ -6,9 +6,6 @@ import yaml def printf(s, v): sys.stdout.write(s % v) -def atoi(s): - return eval(s) - def is_num(s): try: float(s) @@ -257,7 +254,7 @@ def es_split(s): ops_flat = reduce(f, ops, []) kdic = { - 'cmt_s' : [ '/*', '//', '#' ], + 'cmt_s' : [ '/*', '//', '#', '/*-->|', '|<--*/' ], 'cmt_e' : [ '*/' ], 'qt' : [ '"', "'" ], 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, 行末までコメントとして扱う単語として、 '/*-->|' と '|<--*/' の登録を追加してます。 そして卑怯な atoi() を削除してます。 sum3.c の差分を再掲します。 @@ -11,6 +11,13 @@ printf("%d", v); } +/*-->| +void atoi(char *s) +{ + return eval(s) +} +|<--*/ + int sum(int argc, char **argv, int i) { if (i >= argc) return 0;
Cコンパイラでは全体がコメントに見えてますが、 この変態インタプリタでは、1行のコメントが前後にあるだけの関数定義に見えてます。
単語の切り出し処理で、長い予約語から順に認識させているので、 '/*' よりも先に '/*-->|' が行末までのコメントとして認識されます。
そして関数本体が生きた状態で認識されて、 末尾で '*/' よりも先に '|<--*/' が行末までのコメントとして認識されます。
atoi()本体で呼び出してる eval(s)
は、この sum3.c の中に定義はありません。
+def get_sys_func(name): + try: + func = eval(name) + except NameError: + return None + return func +
ここで name の値の文字列 'eval' として、eval() に渡して関数を探してます。 当然見つかるので python の eval 関数の関数オブジェクトが返り...
@@ -451,6 +464,9 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: + func = get_sys_func(name) + if func: + return func(*args) err_exit("not found fdef '{}'".format(name)) (args_info, ret_type, body) = fdef[2:]
ここで返された関数オブジェクトが func に代入されて実行されます。
それでは実行。
$ cp esP.py esQ.py $ cat esQ.py-diff.txt | patch $ ./esQ.py sum3.c sum=0 $ ./esQ.py sum3.c 1 2 3 sum=6 $
つづいてもう一つの寝ざめの悪い原因 printf も何とかしてみます。
その前に、python 側の関数をより便利に呼び出すための改造をば。
インタプリタからの関数呼び出しは 名前 ( ... )
の前提があります。
リスト中の [ 'name', 'xxx' ], [ 'br_s', '(', ... ] な形式のパターンです。
例えばインタプリタだけで実行されるソースコードの箇所に
f_foo = eval("foo");
と書かれてあったとしたら...。
.c のソースコード中に関数 eval() の定義はなく、 python の環境の eval() を探してきて、 その関数オブジェクトを使って、引数 "foo" という文字列を与えて関数が実行されます。
もし python の環境に関数 foo() が定義されていたら、 関数 foo() の関数オブジェクトが呼び出し結果として返り、 ローカル変数の辞書 env に 'f_foo' という名前の変数として登録されます。
インタプリタだけで有効な場所にあるコードなので、 変数 f_foo の型はどうでもよく、 いきなり f_foo が登場しても 'f_foo' というキーで辞書に値が入るだけです。
そして後続に
f_foo();
などと書かれてあったとしたら...。
これはもう、名前, '(' のパターンで、関数呼び出しとして認識されるはずです。
[ 'fcall', 'f_foo', [ 'br_s', '(', [] ] ]
としてリストに格納されてるはずです。ですが...惜しいかな。
@@ -451,6 +464,9 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: + func = get_sys_func(name) + if func: + return func(*args) err_exit("not found fdef '{}'".format(name)) (args_info, ret_type, body) = fdef[2:]
.c に関数定義がなくpython 側にも定義がなければ、あきらめてます。
ここはひとつ、変数に同じ名前があれば python の関数オブジェクトが入ってると思って、 関数呼び出しすることにします。(邪道です)
ではここで printf 対策の全貌を。
$ cat sum4.c #include <stdio.h> #include <stdlib.h> void show_str(char *s) { //|--> printf("%s", s); //<--| /*-->| f = eval("sys.stdout.write"); f(s); |<--*/ } void show_int(int v) { //|--> printf("%d", v); //<--| /*-->| f = eval("int") show_str( str( f(v) ) ); |<--*/ } /*-->| void atoi(char *s) { return eval(s) } |<--*/ int sum(int argc, char **argv, int i) { if (i >= argc) return 0; return atoi( argv[i] ) + sum(argc, argv, i+1); } int main(int argc, char **argv) { int v = sum(argc, argv, 1); show_str("sum="); show_int(v); show_str("\n"); return 0; } $ $ diff -up -L before -L after sum3.c sum4.c --- before +++ after @@ -3,12 +3,26 @@ void show_str(char *s) { + //|--> printf("%s", s); + //<--| + + /*-->| + f = eval("sys.stdout.write"); + f(s); + |<--*/ } void show_int(int v) { + //|--> printf("%d", v); + //<--| + + /*-->| + f = eval("int") + show_str( str( f(v) ) ); + |<--*/ } /*-->| $
そして esR.py-diff.txt 側
@@ -3,9 +3,6 @@ import sys import yaml -def printf(s, v): - sys.stdout.write(s % v) - def is_num(s): try: float(s) 対策するので削除です。 @@ -163,7 +160,7 @@ def tree_op(lst, ops): if k == 'op': return tree_op1(lst, ops, k, v) - if k in [ 'num', 'name', 'arr', 'fcall', 'br_s' ] and len( lst[1:] ) > 0: + if k in [ 'num', 'name', 'arr', 'fcall', 'br_s', 'str' ] and len( lst[1:] ) > 0: e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': ここは本質じゃなく試行錯誤のなごりです。 こうしておけば本来許されてない文字列同士の演算とか、できてしまうなっと。 @@ -254,8 +251,8 @@ def es_split(s): ops_flat = reduce(f, ops, []) kdic = { - 'cmt_s' : [ '/*', '//', '#', '/*-->|', '|<--*/' ], - 'cmt_e' : [ '*/' ], + 'cmt_s' : [ '/*', '//|-->', '//', '#', '/*-->|', '|<--*/' ], + 'cmt_e' : [ '*/', '//<--|' ], 'qt' : [ '"', "'" ], 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, 'br_s' : [ '(', '[', '{' ], またしても変態コメントの追加です。 '//|-->' の単語があればコメント開始で、行末までじゃなく '//<--|' までがコメントになります。 @@ -461,7 +458,8 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: - func = get_sys_func(name) + v = get_env(info).get(name) + func = v if v else get_sys_func(name) if func: return func(*args) err_exit("not found fdef '{}'".format(name)) ここは本質です。 関数定義が見つからなければ、まず変数をあたってみます。 それでもなければ、get_sys_func() で python 側を探します。 見つかった変数の値には get_sys_func() の結果が入ってる前提になってます。
そして C 側
$ diff -up -L before -L after sum3.c sum4.c --- before +++ after @@ -3,12 +3,26 @@ void show_str(char *s) { + //|--> printf("%s", s); + //<--| + + /*-->| + f = eval("sys.stdout.write"); + f(s); + |<--*/ } C コンパイラのときは前半の printf() 側が有効、 変態インタプリタのときは後半が有効です。 (自分で作ってるのでやりたい放題ですね) 前半部分はまともな C コンパイラは前後に '//' の1行コメントがあるだけの printf() に見えます。 ところが、この変態インタプリタは、 単語 '//|-->' からコメントが始まって、 単語 '//<--|' までコメントが続いてると解釈します。 インタプリタだけで有効な後半の箇所では、 python の環境の sys.stdout.write() 関数をとってきて変数 f に格納。 この「変数 f」を経由するのが重要です。 名前, '(' の形式にしないと、関数呼び出しと思ってくれません。 f(s) は無事関数呼び出しとして実行され、 python 側の sys.stdout.write(s) が走ります。 もし python 側の関数を探させようとして直接 sys.stdout.write(s) と書いても関数呼び出しになりません。 '.' は未だ処理を実装してないですが「演算子」と認識されて、 [ 'op', '.', [ 'op', '.', [ 'name', 'sys' ], [ 'name', 'stdout' ] ], [ 'name', 'write' ] ] の次に [ 'br_s', '(', [ [ 'name', 's' ] ] ] となるはずです。 これは 名前, '(' ではなく、演算子, '(' なので、 関数呼び出し [ 'fcall', ... ] の形式に変換されません。 void show_int(int v) { + //|--> printf("%d", v); + //<--| + + /*-->| + f = eval("int") + show_str( str( f(v) ) ); + |<--*/ } こちらもインタプリタでは後半だけが有効で、 まず python の int() 関数を変数 f にとってきます。 int(v) と書いてしまうとインタプリタでは [ 'type', 'int' ], [ 'br_s', '(', [ 'name', 'v' ] ] と解釈されるので、名前, '(' の順ではなく、型, '(' になって、 関数呼び出しになれません。 一度変数 f におさめて f(v) としてこそ関数呼び出しになります。 ここで v には floatの値が入っているので int に変換された値が返り、 str() に渡されます。 str() は .c に定義がなく、ここは素直に python の str() が呼び出されます。 文字列へと変換された値が返り、既出の show_str() に渡して表示します。
それでは実行。
$ cp esQ.py esR.py $ cat esR.py-diff.txt | patch $ ./esR.py sum4.c 1 2 3 sum=6 $ gcc -o sum4 sum4.c $ ./sum4 1 2 3 sum=6 よし一致。 -v つきで実行して、解釈したリストを表示しておきます。 $ ./esR.py -v sum4.c - - fdef - show_str - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', sys.stdout.write] - [etc, ;] - - fcall - f - - br_s - ( - - [name, s] - [etc, ;] - - fdef - show_int - - br_s - ( - - [type, int] - [name, v] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', int] - - fcall - show_str - - br_s - ( - - - fcall - str - - br_s - ( - - - fcall - f - - br_s - ( - - [name, v] - [etc, ;] - - fdef - atoi - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - kwd - return - - fcall - eval - - br_s - ( - - [name, s] - - fdef - sum - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [etc, ','] - [type, int] - [name, i] - [type, int] - - br_s - '{' - - - kwd - if - - br_s - ( - - - op - '>=' - [name, i] - [name, argc] - - kwd - return - [num, '0'] - - kwd - return - - op - + - - fcall - atoi - - br_s - ( - - - arr - argv - - br_s - '[' - - [name, i] - - fcall - sum - - br_s - ( - - [name, argc] - [etc, ','] - [name, argv] - [etc, ','] - - op - + - [name, i] - [num, '1'] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - - op - '=' - [name, v] - - fcall - sum - - br_s - ( - - [name, argc] - [etc, ','] - [name, argv] - [etc, ','] - [num, '1'] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', sum=] - [etc, ;] - - fcall - show_int - - br_s - ( - - [name, v] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', ' '] - [etc, ;] - - kwd - return - [num, '0'] - [etc, ;] do_fcall() name=main args=[1, ['sum4.c']] do_fcall() name=sum args=[1, ['sum4.c'], 1.0] do_fcall() name=show_str args=['sum='] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['sum='] sum=do_fcall() name=show_int args=[0.0] do_fcall() name=eval args=['int'] do_fcall() name=f args=[0.0] do_fcall() name=str args=[0] do_fcall() name=show_str args=['0'] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['0'] 0do_fcall() name=show_str args=['\n'] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['\n'] $
末尾の箇所。
sys.stdout.write が入ってる変数 f 経由の呼び出しで、 関数への引数が '0' の呼び出し直後に改行なしの '0' が表示されて、 関数への引数が '\n' の呼び出し直後に改行されているあたり。
あたり前の動作ですが、趣き深く思えます。
二項演算子の処理では
def do_op2(v, a, b, info): b = do_expr(b, info) if v == '=': return do_set(a, b, info) a = do_expr(a, info) if v == '+': return a + b if v == '-': return a - b if v == '*': return a * b if v == '/': return a / b if v == '==': return a == b if v == '!=': return a != b if v == '<=': return a <= b if v == '<': return a < b if v == '>=': return a >= b if v == '>': return a > b warn_no_sup('do_op2', 'v', v)
となっていて '=' の代入演算子だけ特別扱いになってます。 この類の演算子は他にもあって
ops = [ # term, bind, lst : [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '~=', '>>=', '<<=' ] ], : ]
あと、単項演算子のインクリメント、デクリメント '++', '--' も左辺値を更新するのでお仲間ですね。
$ diff -up -L before -L after sum4.c sum5.c --- before +++ after @@ -34,8 +34,12 @@ void atoi(char *s) int sum(int argc, char **argv, int i) { - if (i >= argc) return 0; - return atoi( argv[i] ) + sum(argc, argv, i+1); + int s = 0; + while (i < argc) { + s += atoi( argv[i] ); + ++i; + } + return s; } int main(int argc, char **argv) $
再帰をループになおしたこのソースの実行を目標に、 とりあえず '+=' と '++' あたりに対応してみます。
@@ -324,9 +324,14 @@ def do_set(e, val, info): warn_no_sup('do_set', 'k', k) def do_op1(v, a, info): + oa = a a = do_expr(a, info) if v == '-': return -a + if v == '++': + return do_set(oa, a+1, info) + if v == '--': + return do_set(oa, a-1, info) warn_no_sup('do_op1', 'v', v) 前置形だけを前提にしてるので、 返り値は do_set() の返す更新後の値を指定してます。 @@ -335,7 +340,15 @@ def do_op2(v, a, b, info): if v == '=': return do_set(a, b, info) + oa = a a = do_expr(a, info) + if v == '+=': + a += b + return do_set(oa, a, info) + if v == '-=': + a -= b + return do_set(oa, a, info) + if v == '+': return a + b if v == '-': 二項演算子の方も同様で、do_set() の返す値を返します。
それでは実行。
$ cp esR.py esS.py $ cat esS.py-diff.txt | patch $ ./esS.py sum5.c 1 2 3 4 5 6 7 8 9 10 sum=55 $
つづいて、単項演算子のインクリメント、デクリメント '++', '--' の後置形をば。
しかしながら、この対応をまじめにやるのは、かなりの難易度かと。
でも後置形をとるのは '++', '--' の2つだけ?
よくありがちなソースの形として
$ diff -up -L before -L after sum5.c sum6.c --- before +++ after @@ -32,19 +32,11 @@ void atoi(char *s) } |<--*/ -int sum(int argc, char **argv, int i) -{ - int s = 0; - while (i < argc) { - s += atoi( argv[i] ); - ++i; - } - return s; -} - int main(int argc, char **argv) { - int v = sum(argc, argv, 1); + int v = 0, i = 1; + + while (i < argc) v += atoi( argv[i++] ); show_str("sum="); show_int(v); show_str("\n"); $ そして一旦ターゲットを次の箇所にします。 + while (i < argc) v += atoi( argv[i++] ); こいつが動けば、とりあえずよしとしましょう。
@@ -164,6 +164,8 @@ def tree_op(lst, ops): e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': + if v1 in [ '++', '--' ]: # !!! + return [ [ k1, (v1, 'back'), e ] ] + tree_op( lst[2:], ops ) return tree_op2(lst, ops, e, k1, v1) return [ e ] + tree_op( lst[1:], ops ) @@ -332,6 +334,12 @@ def do_op1(v, a, info): return do_set(oa, a+1, info) if v == '--': return do_set(oa, a-1, info) + if v == ('++', 'back'): + do_set(oa, a+1, info) + return a + if v == ('--', 'back'): + do_set(oa, a-1, info) + return a warn_no_sup('do_op1', 'v', v) 我ながら、なかなかの手抜きです。 [ k, v, ... ] で、後置形であるという情報を、k, v, の後の ... に入れてしまうと、 単項、二項の区別を ... の要素数で決めているのでマズイです。 v の値を '++' から ('++', 'back') あるいは '--' から ('--', 'back') のタプルに変更して、なんとかごまかしてます。 そして後置形なので、do_set() で更新する前の値を返すようにします。
それでは実行。
$ cp esS.py esT.py $ cat esT.py-diff.txt $ cat sum6.c #include <stdio.h> #include <stdlib.h> void show_str(char *s) { //|--> printf("%s", s); //<--| /*-->| f = eval("sys.stdout.write"); f(s); |<--*/ } void show_int(int v) { //|--> printf("%d", v); //<--| /*-->| f = eval("int") show_str( str( f(v) ) ); |<--*/ } /*-->| void atoi(char *s) { return eval(s) } |<--*/ int main(int argc, char **argv) { int v = 0, i = 1; while (i < argc) v += atoi( argv[i++] ); show_str("sum="); show_int(v); show_str("\n"); return 0; } $ $ ./esT.py sum6.c 1 2 3 4 5 6 7 8 9 10 sum=55 $ よし。 ターゲット + while (i < argc) v += atoi( argv[i++] ); の箇所のリストを見てみると $ ./esT.py -v sum6.c : - - kwd - while - - br_s - ( - - - op - < - [name, i] - [name, argc] - - op - += - [name, v] - - fcall - atoi - - br_s - ( - - - arr - argv - - br_s - '[' - - - op - !!python/tuple [++, back] - [name, i] - [etc, ;] : !!とか出てますが、大丈夫そうですね。 コンパイラでも試してみます。 $ gcc -o sum6 sum6.c $ ./sum6 1 2 3 4 5 6 7 8 9 10 sum=55 $ よしよし。
インクリメント演算子もなんとかなったということで、 ひとつ for 文のループなどを。
個人的に for 文は古来より for (i=0; i<n; i++) ...
などと
インクリメント演算子の後置形と合わせて使われる感じがします。
ではターゲットを。
$ diff -up -L before -L after sum6.c sum7.c --- before +++ after @@ -34,9 +34,9 @@ void atoi(char *s) int main(int argc, char **argv) { - int v = 0, i = 1; + int v = 0, i; - while (i < argc) v += atoi( argv[i++] ); + for (i=1; i<argc; i++) v += atoi( argv[i] ); show_str("sum="); show_int(v); show_str("\n"); $
while の時と同じような対応で。
@@ -199,9 +199,9 @@ def tree_kwd(lst, kdic): if top_is(r, 'kwd', 'else'): e += [ r.pop(0)[2] ] return [ e ] + r - if v == 'while' and len( lst[2:] ) > 0: + if v in [ 'while', 'for' ] and len( lst[2:] ) > 0: if lst[1][:2] != [ 'br_s', '(' ]: - err_exit("not found '(' after 'while'") + err_exit("not found '(' after '{}'".format(v)) r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] return [ e ] + r while () ... for () ... ()カッコの中の形式以外は同じ構造なので、while に混ぜてもらってます。 @@ -261,7 +261,7 @@ def es_split(s): 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], - 'kwd' : [ 'return', 'if', 'else', 'while' ], + 'kwd' : [ 'return', 'if', 'else', 'while', 'for' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';', ',' ], } 'kwd' に 'for' を登録してます。 @@ -418,6 +418,13 @@ def do_kwd(v, expr, info): while do_expr( expr[2], info ): do_expr( expr[3], info ) return None + if v == 'for': + args = expr[2][2][::2] + do_expr( args[0], info ) + while do_expr( args[1], info ): + do_expr( expr[3], info ) + do_expr( args[2], info ) + return None warn_no_sup('do_kwd', 'v', v) Python の for とは違うのでここは while で処理を追加してみます。
それでは実行。
$ cp esT.py esU.py $ cat esU.py-diff.txt | patch $ ./esU.py sum7.c 1 2 3 $ sum=6 $
では試練を。
$ diff -up -L before -L after sum7.c sum8.c --- before +++ after @@ -34,9 +34,9 @@ void atoi(char *s) int main(int argc, char **argv) { - int v = 0, i; + int v, i; - for (i=1; i<argc; i++) v += atoi( argv[i] ); + for (v=0,i=1; i<argc; i++) v += atoi( argv[i] ); show_str("sum="); show_int(v); show_str("\n"); $ $ ./esU.py -v sum8.c - - fdef - show_str - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', sys.stdout.write] - [etc, ;] - - fcall - f - - br_s - ( - - [name, s] - [etc, ;] - - fdef - show_int - - br_s - ( - - [type, int] - [name, v] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', int] - - fcall - show_str - - br_s - ( - - - fcall - str - - br_s - ( - - - fcall - f - - br_s - ( - - [name, v] - [etc, ;] - - fdef - atoi - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - kwd - return - - fcall - eval - - br_s - ( - - [name, s] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [etc, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - [name, v] - [etc, ','] - [name, i] - [etc, ;] - - kwd - for - - br_s - ( - - - op - '=' - [name, v] - [num, '0'] - [etc, ','] - - op - '=' - [name, i] - [num, '1'] - [etc, ;] - - op - < - [name, i] - [name, argc] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i] - - op - += - [name, v] - - fcall - atoi - - br_s - ( - - - arr - argv - - br_s - '[' - - [name, i] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', sum=] - [etc, ;] - - fcall - show_int - - br_s - ( - - [name, v] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', ' '] - [etc, ;] - - kwd - return - [num, '0'] - [etc, ;] do_fcall() name=main args=[1, ['sum8.c']] Err over acc argv[1] $
main 関数は argc=1 で呼び出しているので、argv[1] は確かにオーバーアクセスです。 ですがその前に。
- - kwd - for - - br_s - ( - - - op - '=' - [name, v] - [num, '0'] - [etc, ','] - - op - '=' - [name, i] - [num, '1'] - [etc, ;] - - op - < - [name, i] - [name, argc] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i]
for 文の () の中のリストの長さが 7 になってます。
+ if v == 'for': + args = expr[2][2][::2] + do_expr( args[0], info ) + while do_expr( args[1], info ): + do_expr( expr[3], info ) + do_expr( args[2], info ) + return None
ここの expr[2][2][::2]
では、
リスト expr[2][2] の長さが 5 で、[ a, ';', b, ';', c, ';' ] の並びを期待してて、
[::2] でインデックスが0,2,4の偶数だけのリストにしてます。
',' 演算子。これを何とかせねばなりませぬ。
ここまでは単語 ',' を放置してきました。
ops = [ # term, bind, lst [ 2, '>', [ '->', '.' ] ], [ 1, '<', [ '!', '~', '++', '--', '-', '*', '&' ] ], # dup -,*,& [ 2, '>', [ '*', '/', '%' ] ], # dup * [ 2, '>', [ '+', '-' ] ], # dup - [ 2, '>', [ '<<', '>>' ] ], [ 2, '>', [ '<', '<=', '>', '>=' ] ], [ 2, '>', [ '==', '!=' ] ], [ 2, '>', [ '&' ] ], # dup & [ 2, '>', [ '^' ] ], [ 2, '>', [ '|' ] ], [ 2, '>', [ '&&' ] ], [ 2, '>', [ '||' ] ], [ 3, '<', [ '?', ':' ] ], [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '~=', '>>=', '<<=' ] ], # [ 2, '>', [ ',' ] ], # ... ] f = lambda t, (term, bind, lst): t + [ v for v in lst if v not in t ] ops_flat = reduce(f, ops, []) kdic = { 'cmt_s' : [ '/*', '//|-->', '//', '#', '/*-->|', '|<--*/' ], 'cmt_e' : [ '*/', '//<--|' ], 'qt' : [ '"', "'" ], 'esc' : { '\\n':'\n', '\\t':'\t', '\\"':'"', "\\'":"'", '\\\\':'\\' }, 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], 'kwd' : [ 'return', 'if', 'else', 'while', 'for' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';', ',' ], }
演算子用の ops テーブルでは、しれっとコメントアウトしてて、 単語のグループ分け用の kdic では 'etc' グループに ';' と一緒に入ってます。
ここで ',' を優先度が最も低い二項演算子として認めてみましょう。
@@ -246,7 +246,7 @@ def es_split(s): [ 2, '>', [ '||' ] ], [ 3, '<', [ '?', ':' ] ], [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '~=', '>>=', '<<=' ] ], - # [ 2, '>', [ ',' ] ], # ... + [ 2, '>', [ ',' ] ], # ... ] f = lambda t, (term, bind, lst): t + [ v for v in lst if v not in t ] テーブル ops のコメントアウトしてた箇所を解禁してます。 @@ -263,7 +263,7 @@ def es_split(s): 'type' : [ 'int', 'char', 'void' ], 'kwd' : [ 'return', 'if', 'else', 'while', 'for' ], 'spc' : [ ' ', '\t', '\n' ], - 'etc' : [ ';', ',' ], + 'etc' : [ ';' ], } enc = lambda ki, idx: '@{:02d}{:02d}'.format(ki, idx) 逆に kdic 側の登録は抹消してます。 @@ -377,6 +377,8 @@ def do_op2(v, a, b, info): return a >= b if v == '>': return a > b + if v == ',': + return b warn_no_sup('do_op2', 'v', v) 二項演算子としての ',' の処理は、左辺と右辺を評価して、右辺側の値を返すだけです。 @@ -428,6 +430,11 @@ def do_kwd(v, expr, info): warn_no_sup('do_kwd', 'v', v) +def do_comma(e, info): + if e[:2] != ['op', ',']: + return [ do_expr(e, info) ] + return do_comma( e[2], info ) + [ do_expr( e[3], info ) ] + def do_expr(expr, info): if info.get('verb') >= 2: print('expr={}'.format(expr)) @@ -453,7 +460,8 @@ def do_expr(expr, info): return do_arr( v, expr[2], info ) if k == 'fcall': (_, _, args) = expr[2] - args = [ do_expr(e, info) for e in args if e != [ 'etc', ',' ] ] + if len(args) > 0: + args = do_comma( args[0], info ) return do_fcall(v, args, info) if k in [ 'type', 'fproto', 'etc' ]: # do nothing !!! ここが肝です。 関数呼び出しの引数は、空の場合と、引数が1つの場合と、2つ以上の場合があります。 まず空の場合は、従来通り空のリストを do_fcall() に渡して問題ないはずです。 引数が1つ場合も、それを評価した結果の値を長さ1のリストとして do_fcall() に渡せばいいです。 問題は引数が2つ以上の場合。 tree_op() の処理で引数のリスト構造は、優先順位が最も低い ',' 演算子が 最外殻に出た形にまとめあげられているはずです。 例えば引数が3つなら [ 'br_s' '(', [ [ 'op', ',', [ 'op', ',' <1つ目の引数>, <2つ目の引数> ], <3つ目の引数> ] ] ] この先頭の [ 'op', ',', ... ] を do_expr() で評価すると、 順次全ての引数が評価されていきますが、結果としては最後の引数の評価結果が返るのみです。 コンマ演算子の評価とはそういうもののはずです。 でもこれでは関数呼び出しの引数の評価としては使えません。 そこで do_comma(e, info) 関数を追加しています。 e : [ 種別, 単語, { 配下の階層や付随する情報など... } ] の形式です。 info : その他、実行のための情報をひっくるめた辞書です。 e として [ 'op', ',', ... ] を渡すと、 [ 'op', ',', [ 'op', ',' <1つ目の引数>, <2つ目の引数> ], <3つ目の引数> ] このようなネストになってる事を期待して、各引数の評価結果をフラットなリストにして返します。 当然再帰を使っているので、終端条件として e にコンマ演算子以外が渡ると、 e の評価結果を長さ1のリストにして返します。 これはそのまま、引数が1つの場合の処理に当てはまるので、 関数の引数が1以上の場合、ひっくるめて do_comma() を呼び出しています。
それでは前回敗北した sum8.c で実行。
$ diff -up -L before -L after sum7.c sum8.c --- before +++ after @@ -34,9 +34,9 @@ void atoi(char *s) int main(int argc, char **argv) { - int v = 0, i; + int v, i; - for (i=1; i<argc; i++) v += atoi( argv[i] ); + for (v=0,i=1; i<argc; i++) v += atoi( argv[i] ); show_str("sum="); show_int(v); show_str("\n"); $ $ cp esU.py esV.py $ cat esV.py-diff.txt | patch $ ./esV.py -v sum8.c - - fdef - show_str - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', sys.stdout.write] - [etc, ;] - - fcall - f - - br_s - ( - - [name, s] - [etc, ;] - - fdef - show_int - - br_s - ( - - [type, int] - [name, v] - [type, void] - - br_s - '{' - - - op - '=' - [name, f] - - fcall - eval - - br_s - ( - - [str, '"', int] - - fcall - show_str - - br_s - ( - - - fcall - str - - br_s - ( - - - fcall - f - - br_s - ( - - [name, v] - [etc, ;] - - fdef - atoi - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - - kwd - return - - fcall - eval - - br_s - ( - - [name, s] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [op, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - [type, int] - - op - ',' - [name, v] - [name, i] - [etc, ;] - - kwd - for - - br_s - ( - - - op - ',' - - op - '=' - [name, v] - [num, '0'] - - op - '=' - [name, i] - [num, '1'] - [etc, ;] - - op - < - [name, i] - [name, argc] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i] - - op - += - [name, v] - - fcall - atoi - - br_s - ( - - - arr - argv - - br_s - '[' - - [name, i] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', sum=] - [etc, ;] - - fcall - show_int - - br_s - ( - - [name, v] - [etc, ;] - - fcall - show_str - - br_s - ( - - [str, '"', ' '] - [etc, ;] - - kwd - return - [num, '0'] - [etc, ;] do_fcall() name=main args=[1, ['sum8.c']] do_fcall() name=show_str args=['sum='] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['sum='] sum=do_fcall() name=show_int args=[0.0] do_fcall() name=eval args=['int'] do_fcall() name=f args=[0.0] do_fcall() name=str args=[0] do_fcall() name=show_str args=['0'] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['0'] 0do_fcall() name=show_str args=['\n'] do_fcall() name=eval args=['sys.stdout.write'] do_fcall() name=f args=['\n'] $ 問題の for 文の箇所は - - kwd - for - - br_s - ( - - - op - ',' - - op - '=' - [name, v] - [num, '0'] - - op - '=' - [name, i] - [num, '1'] - [etc, ;] - - op - < - [name, i] - [name, argc] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i] '(' の中のリストの要素数は 5 です。 $ ./esV.py sum8.c 1 2 3 4 5 6 7 8 9 10 sum=55 $ (...) にコンマを含む for 文の実行は大丈夫そうです。 でも関数が引数が2個以上のものが無いですね。 では以前の uhyo.c で。 $ cat uhyo.c int uhyo(int a, int b); int sub(int v) { int return_value = (v + 1) * 2; return return_value; } int uhyo(int a, int b) { /* comment*/ char *s = " hello @07 /* "; char *s2 = "world"; char *s3 = " */ \" \n"; int vec[100]; int sub(int v); a = sub(sub(b)); /* " */ vec[a+sub(a)] = 1; return a + b * 2; /* @01 " */ } int main(int argc, char **argv) { return uhyo(1, 2); } $ $ ./esV.py uhyo.c $ echo $? 18 $ 確か 18 で良かったはずです。 引数2つはとりあえず大丈夫そうです。
演算子のまとめ にやっかいなバグがありました。
: このあたり、処理中の演算子と、仮処理済の後続の演算子で、 優先順位を比較して、辺の並びかえでゴニョゴニョして、 かなりややこしくなってます。 :
などと書いてましたが、やっぱりバグっておりました。
例えば
$ cat a3.c int a3(int a, int b, int c) { return a + b + c; } int main(int argc, char **argv) { return a3(1, 2, 3); } $ $ ./esV.py a3.c ; echo $? 6 $
これは良し。
$ cat a4.c int a4(int a, int b, int c, int d) { return a + b + c + d; } int main(int argc, char **argv) { return a4(1, 2, 3, 4); } $ $ ./esV.py a4.c ; echo $? Traceback (most recent call last): File "./esV.py", line 544, inrcode = do_fcall( 'main', args, info ) File "./esV.py", line 510, in do_fcall do_blk(body, info) File "./esV.py", line 477, in do_blk do_expr(expr, info) File "./esV.py", line 443, in do_expr return do_kwd(v, expr, info) File "./esV.py", line 410, in do_kwd r = do_expr( expr[2], info ) File "./esV.py", line 465, in do_expr return do_fcall(v, args, info) File "./esV.py", line 510, in do_fcall do_blk(body, info) File "./esV.py", line 477, in do_blk do_expr(expr, info) File "./esV.py", line 443, in do_expr return do_kwd(v, expr, info) File "./esV.py", line 410, in do_kwd r = do_expr( expr[2], info ) File "./esV.py", line 458, in do_expr return do_op( v, expr[2:], info ) File "./esV.py", line 390, in do_op return do_op2(v, args[0], args[1], info) File "./esV.py", line 361, in do_op2 return a + b TypeError: unsupported operand type(s) for +: 'float' and 'NoneType' 1 $
引数3つの関数呼び出しは成功してるのに、引数4つだと失敗します。
「1, 2, たくさん」じゃ無いですが、 「なしのパターン」、「1つのパターン」、「2つのパターン」が成功すれば、 たいがい「2つ以上のパターン」も成功しそうなものですが...。
これはその上をいってて、「3つのパターン」まで問題なくて、 「4つ以上のパターン」で初めて健在化するという凶悪なバグです。
リストの構造を見てみます。
$ ./esV.py -v a4.c - - fdef - a4 - - br_s - ( - - [type, int] - [name, a] - [op, ','] - [type, int] - [name, b] - [op, ','] - [type, int] - [name, c] - [op, ','] - [type, int] - [name, d] - [type, int] - - br_s - '{' - - - kwd - return - - op - + - - op - + - [name, a] - - op - + - [name, b] - [name, c] - [name, d] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [op, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - a4 - - br_s - ( - - - op - ',' - - op - ',' - [num, '1'] - - op - ',' - [num, '2'] - [num, '3'] - [num, '4'] - [etc, ;] do_fcall() name=main args=[1, ['a4.c']] do_fcall() name=a4 args=[1.0, 3.0, 4.0] Traceback (most recent call last): File "./esV.py", line 544, inrcode = do_fcall( 'main', args, info ) File "./esV.py", line 510, in do_fcall do_blk(body, info) File "./esV.py", line 477, in do_blk do_expr(expr, info) File "./esV.py", line 443, in do_expr return do_kwd(v, expr, info) File "./esV.py", line 410, in do_kwd r = do_expr( expr[2], info ) File "./esV.py", line 465, in do_expr return do_fcall(v, args, info) File "./esV.py", line 510, in do_fcall do_blk(body, info) File "./esV.py", line 477, in do_blk do_expr(expr, info) File "./esV.py", line 443, in do_expr return do_kwd(v, expr, info) File "./esV.py", line 410, in do_kwd r = do_expr( expr[2], info ) File "./esV.py", line 458, in do_expr return do_op( v, expr[2:], info ) File "./esV.py", line 390, in do_op return do_op2(v, args[0], args[1], info) File "./esV.py", line 361, in do_op2 return a + b TypeError: unsupported operand type(s) for +: 'float' and 'NoneType' $ 関数 a4() の定義側 - return - - op - + - - op - + - [name, a] - - op - + - [name, b] - [name, c] - [name, d] そして関数呼び出し側 - - - op - ',' - - op - ',' - [num, '1'] - - op - ',' - [num, '2'] - [num, '3'] - [num, '4'] 確かにバグってます。 (((1, 2), 3), 4) にまとめあげて欲しいところが、 ((1, (2, 3)), 4) になってます。
という事は問題は二項演算子をまとめる処理 tree_op2() が怪しい。
def tree_op2(lst, ops, e, k1, v1): r = tree_op( lst[2:], ops ) if len(r) == 0: err_exit("not found right term op='{}'".format(v1)) (k2, v2) = r[0][:2] if k2 == 'op': i1 = ops_idx(ops, 2, v1) i2 = ops_idx( ops, len( r[0][2:] ), v2 ) if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] return [ [ k1, v1, e, r[0] ] ] + r[1:]
同じ演算子が並ぶので優先度は同じ。
return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:]
辺の並び替えでゴニョゴニョしてる、ここに問題がありました。
ここの処理では例えば 1 + 2 + 3 ... としたら e : 1 k2, v2 : op, + r : [ [ op, +, 2, 3 ], ... ] r[0] : [ op, +, 2, 3 ] r[1:] : [ ... ] な場面で r[0][2] : 2 r[0][3] : 3 r[0] の 2, 3 から 2 をとってきて [ k1, v1, e, r[0][2] ] で [ op, +, 1, 2 ] にまとめ直して [ k2, v2, [ op, +, 1, 2 ], r[0][3] ] で [ op, +, [ op, +, 1, 2 ], 3 ] に。 最終的に [ [ op, +, [ op, +, 1, 2 ], 3 ], ... ] を返す処理です。 4つ以上で考えてみたら、確かにここで e : 1 k2, v2 : op, + r : [ [ op, +, [ op, +, 2, 3 ], 4 ], ... ] r[0] : [ op, +, [ op, +, 2, 3 ], 4 ] r[1:] : [ ... ] r[0][2] : [ op, +, 2, 3 ] r[0][3] : 4 [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] なので [ [ op, +, [ op, +, 1, [ op, +, 2, 3 ] ], 4 ] ] + [ ... ] となって [ [ op, +, [ op, +, 1, [ op, +, 2, 3 ] ], 4 ] ] つまり (( 1 + (2 + 3 )) + 4 ) になってます。 問題の本質は [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] の箇所で [ k1, v1, e, r[0][2] ] を固定してるところに在りました。 演算子 v1 の配下に無条件で r[0][2] を入れてますが、 r[0][2] が v1 より優先度が低い(あるいは同じ優先度でも結合規則が '>' 方向な) 演算子の場合に、 マズイです。 1 と (2 + 3) に対しても再帰的に処理をして、 ((1 + 2) + 3) に繋ぎ変えてから処理せねばなりませんでした。
@@ -132,17 +132,21 @@ def tree_op1(lst, ops, k, v): return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] return [ [ k, v, r[0] ] ] + r[1:] +def op2(ops, e, k1, v1, r0): + (k2, v2) = r0[:2] + if k2 == 'op': + i1 = ops_idx(ops, 2, v1) + i2 = ops_idx( ops, len( r0[2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + r02 = op2( ops, e, k1, v1, r0[2] ) + return [ k2, v2, r02, r0[3] ] + return [ k1, v1, e, r0 ] + def tree_op2(lst, ops, e, k1, v1): r = tree_op( lst[2:], ops ) if len(r) == 0: err_exit("not found right term op='{}'".format(v1)) - (k2, v2) = r[0][:2] - if k2 == 'op': - i1 = ops_idx(ops, 2, v1) - i2 = ops_idx( ops, len( r[0][2:] ), v2 ) - if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): - return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] - return [ [ k1, v1, e, r[0] ] ] + r[1:] + return [ op2( ops, e, k1, v1, r[0] ) ] + r[1:] def tree_op(lst, ops): if len(lst) == 0: tree_op2() の後半の処理を op2() にのれん分けしてます。 op2() では、問題の箇所で再帰で r02 を求めてます。
それでは実行。
$ cp esV.py esW.py $ cat esW.py-diff.txt | patch $ ./esW.py a4.c ; echo $? 10 $ $ ./esW.py -v a4.c - - fdef - a4 - - br_s - ( - - [type, int] - [name, a] - [op, ','] - [type, int] - [name, b] - [op, ','] - [type, int] - [name, c] - [op, ','] - [type, int] - [name, d] - [type, int] - - br_s - '{' - - - kwd - return - - op - + - - op - + - - op - + - [name, a] - [name, b] - [name, c] - [name, d] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [op, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - a4 - - br_s - ( - - - op - ',' - - op - ',' - - op - ',' - [num, '1'] - [num, '2'] - [num, '3'] - [num, '4'] - [etc, ;] do_fcall() name=main args=[1, ['a4.c']] do_fcall() name=a4 args=[1.0, 2.0, 3.0, 4.0] $
ねんのため
$ cat a10.c int a10(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10) { return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10; } int main(int argc, char **argv) { return a10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } $ $ ./esW.py a10.c ; echo $? 55 $ $ ./esW.py -v a10.c - - fdef - a10 - - br_s - ( - - [type, int] - [name, a1] - [op, ','] - [type, int] - [name, a2] - [op, ','] - [type, int] - [name, a3] - [op, ','] - [type, int] - [name, a4] - [op, ','] - [type, int] - [name, a5] - [op, ','] - [type, int] - [name, a6] - [op, ','] - [type, int] - [name, a7] - [op, ','] - [type, int] - [name, a8] - [op, ','] - [type, int] - [name, a9] - [op, ','] - [type, int] - [name, a10] - [type, int] - - br_s - '{' - - - kwd - return - - op - + - - op - + - - op - + - - op - + - - op - + - - op - + - - op - + - - op - + - - op - + - [name, a1] - [name, a2] - [name, a3] - [name, a4] - [name, a5] - [name, a6] - [name, a7] - [name, a8] - [name, a9] - [name, a10] - [etc, ;] - - fdef - main - - br_s - ( - - [type, int] - [name, argc] - [op, ','] - [type, char, '*', '*'] - [name, argv] - [type, int] - - br_s - '{' - - - kwd - return - - fcall - a10 - - br_s - ( - - - op - ',' - - op - ',' - - op - ',' - - op - ',' - - op - ',' - - op - ',' - - op - ',' - - op - ',' - - op - ',' - [num, '1'] - [num, '2'] - [num, '3'] - [num, '4'] - [num, '5'] - [num, '6'] - [num, '7'] - [num, '8'] - [num, '9'] - [num, '10'] - [etc, ;] do_fcall() name=main args=[1, ['a10.c']] do_fcall() name=a10 args=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] $
大丈夫みたいですね。
では試練を。
$ cat for.c #include <stdio.h> #include <stdlib.h> void show_str(char *s) { //|--> printf("%s", s); //<--| /*-->| f = eval("sys.stdout.write"); f(s); |<--*/ } void show_int(int v) { //|--> printf("%d", v); //<--| /*-->| f = eval("int") show_str( str( f(v) ) ); |<--*/ } /*-->| void atoi(char *s) { return eval(s) } |<--*/ void show_res(int lv, int v) { show_str("ck"); show_int(lv); show_str(":"); show_int(v); show_str("\n"); } int main(int argc, char **argv) { int lv = 100, lc = 0, v, i; if (argc > 1) lv = atoi( argv[1] ); if (lv >= ++lc) { v = 0; for (i=1; i<=10; i++) v += i; show_res(lc, v); } if (lv >= ++lc) { for (v=0,i=1; i<=10; i++) v += i; show_res(lc, v); } if (lv >= ++lc) { for (v=0,i=1; i<=10; v+=i,i++); show_res(lc, v); } if (lv >= ++lc) { for (v=0,i=1; i<=10; v+=i,++i); show_res(lc, v); } if (lv >= ++lc) { v = 0; i = 1; for (; i<=10; ) v += i++; show_res(lc, v); } if (lv >= ++lc) { v = 0; i = 1; for (;;) if (i<=10) v += i++; else break; show_res(lc, v); } if (lv >= ++lc) { for (v=0,i=-10; i<=10; i++) if (i <= 0) continue; else v += i; show_res(lc, v); } return 0; } $ $ gcc -o for for.c $ ./for ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 $ $ ./for 1 ck1:55 $ $ ./for 5 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 $ コンパイルして実行するとこのような感じです。 各 if 文のブロックでは色々なパターンの for 文で、 1 から 10 を足し算して表示します。 コマンド引数を指定すると、指定したレベルの if 文までの実行になります。
それではお試し。
$ ./esW.py for.c 1 Err not found term=1 op='('++', 'back')' in ops $ $ ./esW.py -v for.c 1 Err not found term=1 op='('++', 'back')' in ops $
これは少々難易度が高過ぎたかも。 実行以前に構文のリストを作る段階でしくじってますね。
しかもこのエラーメッセージだけでは何とも...。
エラーメッセージを出してる箇所そのものは def ops_idx(ops, term, op): i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) if i is None: err_exit("not found term={} op='{}' in ops".format(term, op)) return i ここのはずで、term=1 なので呼び出し元は def tree_op1(lst, ops, k, v): r = tree_op( lst[1:], ops ) if len(r) == 0: err_exit("not found rignt term op='{}'".format(v)) (k2, v2) = r[0][:2] if k2 == 'op': i1 = ops_idx(ops, 1, v) i2 = ops_idx( ops, len( r[0][2:] ), v2 ) if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] return [ [ k, v, r[0] ] ] + r[1:] テーブル ops 見つからないと言ってる ('++', 'back') は、 元々 ops には登録してません。 tree_op1() の呼び元であるところの def tree_op(lst, ops): : if k == 'op': return tree_op1(lst, ops, k, v) if k in [ 'num', 'name', 'arr', 'fcall', 'br_s', 'str' ] and len( lst[1:] ) > 0: e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': if v1 in [ '++', '--' ]: # !!! return [ [ k1, (v1, 'back'), e ] ] + tree_op( lst[2:], ops ) return tree_op2(lst, ops, e, k1, v1) return [ e ] + tree_op( lst[1:], ops ) ここで tree_op1() を呼びださなかったときに、 後置形と判断したとき、('++', 'back') に置き換えて、さらに呼び元に戻してます。 基本的に tree_op() から tree_op1(), tree_op2() で処理された [ 'op', ... ] は、 再び tree_op() で処理されることは無いはず? それにしても '++' の後置形という情報だけ。 なんとも for.c のどこなんでしょう?
tree_op() に呼び出しの記録を追加してみます。
@@ -114,15 +114,30 @@ def name_bra(lst): return [ f( *lst[:pn] ) ] + name_bra(lst[pn:]) return [ lst[0] ] + name_bra(lst[1:]) +tstk = [] + +def dup_lst(v): + return [ dup_lst(e) if type(e) == list else e for e in v ]if type(v) == list else v + +def tstk_push(v): + global tstk + tstk.append( dup_lst(v) ) + +def tstk_pop_ret(v): + tstk.pop() + return v + グローバル変数 tstk ( tree_xxx() 関連用のスタック ) を用意して、 引数で指定したデータをスタックへ記録する関数 ttsk_push(v) と、 スタック上の記録を1つ削除しつつ、引数で指定した値を返す関数 tsk_pop_ret(v) を追加してます。 tsk_push(v) の引数 v には構文リスト構築途中経過のリストが含まれるので、 dup_lst(v) を用意してリストの深いコピーをとってます。 def ops_idx(ops, term, op): i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) if i is None: + print yaml.dump(tstk) err_exit("not found term={} op='{}' in ops".format(term, op)) return i def tree_op1(lst, ops, k, v): r = tree_op( lst[1:], ops ) if len(r) == 0: + print yaml.dump(tstk) err_exit("not found rignt term op='{}'".format(v)) (k2, v2) = r[0][:2] if k2 == 'op': @@ -145,12 +160,14 @@ def op2(ops, e, k1, v1, r0): def tree_op2(lst, ops, e, k1, v1): r = tree_op( lst[2:], ops ) if len(r) == 0: + print yaml.dump(tstk) err_exit("not found right term op='{}'".format(v1)) return [ op2( ops, e, k1, v1, r[0] ) ] + r[1:] tree_op() 配下の処理で err_exit() してる箇所に、 tstk の内容を yaml形式で表示する処理を追加しています。 def tree_op(lst, ops): + tstk_push( [ 'op', lst ] ) if len(lst) == 0: - return lst + return tstk_pop_ret(lst) e = lst[0] (k, v) = e[:2] @@ -162,17 +179,18 @@ def tree_op(lst, ops): e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] if k == 'op': - return tree_op1(lst, ops, k, v) + return tstk_pop_ret( tree_op1(lst, ops, k, v) ) if k in [ 'num', 'name', 'arr', 'fcall', 'br_s', 'str' ] and len( lst[1:] ) > 0: e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': if v1 in [ '++', '--' ]: # !!! - return [ [ k1, (v1, 'back'), e ] ] + tree_op( lst[2:], ops ) - return tree_op2(lst, ops, e, k1, v1) + ret = [ [ k1, (v1, 'back'), e ] ] + tree_op( lst[2:], ops ) + return tstk_pop_ret(ret) + return tstk_pop_ret( tree_op2(lst, ops, e, k1, v1) ) - return [ e ] + tree_op( lst[1:], ops ) + return tstk_pop_ret( [ e ] + tree_op( lst[1:], ops ) ) def tree_kwd(lst, kdic): if len(lst) == 0: tree_op(lst, ops) 関数本体では、 入ってきたところで tstk_push( [ 'op', lst ] ) の呼び出しを追加して、 lst の深いコピーをスタックに追加してます。 一応 tree_op() 以外でも使うかも知れないので、'op' という情報も入れてます。 そして、tree_op() から return する箇所では tsk_pop_ret() をかまして、 入口でスタックに積んだデータを削除しつつ値を返してます。
それでは実行。
$ cp esW.py esX.py $ cat esX.py-diff.txt | patch $ ./esX.py for.c - - op - - - fdef - show_str - - br_s - ( - - [type, char, '*'] - [name, s] - [type, void] - - br_s - '{' - - [name, f] - [op, '='] - - fcall - eval - - br_s - ( - - [str, '"', sys.stdout.write] - [etc, ;] - - fcall - f - - br_s - ( - - [name, s] - [etc, ;] - - fdef - show_int : - - op - - [etc, ;] - [name, v] - [op, +=] - [name, i] - [op, ','] - [name, i] - [op, ++] - - op - - [name, v] - [op, +=] - [name, i] - [op, ','] - [name, i] - [op, ++] - - op - - [name, i] - [op, ','] - [name, i] - [op, ++] Err not found term=1 op='('++', 'back')' in ops $ 大量に表示がでましたが、 エラー直前に積まれたスタックのデータからして、 v+=i,i++ このパターンは for.c の if (lv >= ++lc) { for (v=0,i=1; i<=10; v+=i,++i); show_res(lc, v); } ここの for文 のカッコの中だけです。
なー。 コンマ演算子 ',' と、インクリメント演算子 '++' の後置形。
なるほど。 ',' の右辺の処理で ('++', 'back') が作られて、 ',' 演算子との優先順位の比較でゴニョゴニョしようとして、 ('++', 'back') は テーブル ops に登録が見つからずにエラー。
他にもまだまだありそうな雰囲気ですが、 ここはとりあえず def ops_idx(ops, term, op): i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) if i is None: print yaml.dump(tstk) err_exit("not found term={} op='{}' in ops".format(term, op)) return i ここで引数 op が (xxx, 'back') 形式なら xxx として扱うようにして、 先に進むしかないですね。
@@ -128,6 +128,8 @@ def tstk_pop_ret(v): return v def ops_idx(ops, term, op): + if type(op) == tuple and op[1] == 'back': + op = op[0] i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) if i is None: print yaml.dump(tstk)
それでは実行。
$ cp esX.py esY.py $ cat esY.py-diff.txt | patch $ ./esY.py for.c ck1:55 ck2:55 ck3:65 ck4:65 ck5:0 Traceback (most recent call last): File "./esY.py", line 568, inrcode = do_fcall( 'main', args, info ) File "./esY.py", line 534, in do_fcall do_blk(body, info) File "./esY.py", line 501, in do_blk do_expr(expr, info) File "./esY.py", line 467, in do_expr return do_kwd(v, expr, info) File "./esY.py", line 439, in do_kwd do_expr( expr[3], info ) File "./esY.py", line 479, in do_expr do_blk(expr, info) File "./esY.py", line 501, in do_blk do_expr(expr, info) File "./esY.py", line 467, in do_expr return do_kwd(v, expr, info) File "./esY.py", line 450, in do_kwd while do_expr( args[1], info ): IndexError: list index out of range $ なんとか構文リストは作れたようで実行されてますが、 ck3:65 これは聞き捨てなりません。 for (v=0,i=1; i<=10; v+=i,i++);
ああ、コンマ演算子だ!
関数呼び出し用のじゃなくて、通常のコンマ演算子の処理なので do_op2() です。
def do_op2(v, a, b, info): b = do_expr(b, info) if v == '=': return do_set(a, b, info) oa = a a = do_expr(a, info) if v == '+=': a += b return do_set(oa, a, info) if v == '-=': a -= b return do_set(oa, a, info) if v == '+': return a + b if v == '-': return a - b if v == '*': return a * b if v == '/': return a / b if v == '==': return a == b if v == '!=': return a != b if v == '<=': return a <= b if v == '<': return a < b if v == '>=': return a >= b if v == '>': return a > b if v == ',': return b warn_no_sup('do_op2', 'v', v) 左辺 a , 右辺 b ですが、 右辺の b から先に評価して、そのあとで左辺の a を評価してました。 最終的に右辺の b の評価結果を返していますが、 順番がまずかったです。 代入系の演算子の処理のために、 左辺の評価を出来るだけ先伸ばししようとして、こんな事になってました。 ケチな事をせずに a, b の順で評価するように修正しておきます。
@@ -368,12 +368,11 @@ def do_op1(v, a, info): warn_no_sup('do_op1', 'v', v) def do_op2(v, a, b, info): - b = do_expr(b, info) - if v == '=': - return do_set(a, b, info) - oa = a a = do_expr(a, info) + b = do_expr(b, info) + if v == '=': + return do_set(oa, b, info) if v == '+=': a += b return do_set(oa, a, info)
それでは実行。
$ cp esY.py esZ.py $ cat esZ.py-diff.txt | patch $ ./esZ.py for.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:0 Traceback (most recent call last): File "./esZ.py", line 567, inrcode = do_fcall( 'main', args, info ) File "./esZ.py", line 533, in do_fcall do_blk(body, info) File "./esZ.py", line 500, in do_blk do_expr(expr, info) File "./esZ.py", line 466, in do_expr return do_kwd(v, expr, info) File "./esZ.py", line 438, in do_kwd do_expr( expr[3], info ) File "./esZ.py", line 478, in do_expr do_blk(expr, info) File "./esZ.py", line 500, in do_blk do_expr(expr, info) File "./esZ.py", line 466, in do_expr return do_kwd(v, expr, info) File "./esZ.py", line 449, in do_kwd while do_expr( args[1], info ): IndexError: list index out of range ck3, ck4 は正しく 55 になりました。 ck5:0 やいかに? if (lv >= ++lc) { v = 0; i = 1; for (; i<=10; ) v += i++; show_res(lc, v); } あー。これ。 for 文のカッコの中の3つの式は、どれも省略可能でした。 2つめの条件の式を省略すると「真」扱いですね。 余談ですが、メモリが貴重な昔々はソースコードのサイズも気にして while(1) よりも for(;;) の方が1バイト少なくて済む。 などと伝え聞いた事があります。なんかすごいです。 戻って、for 文のカッコの中の処理は do_kwd() 関数の if v == 'for': args = expr[2][2][::2] do_expr( args[0], info ) while do_expr( args[1], info ): do_expr( expr[3], info ) do_expr( args[2], info ) return None ここの args = expr[2][2][::2] expr[2][2] のリスト [ a, ';', b, ';', c ] から [::2] で [ a, b, c ] にする事を期待してます。 ここがまずかったですね。
ここで大きな問題が!
ファイル名 esZ.py まで到達して枯渇してしまいました。
ちょっと判りにくいかもしれませんが、小文字に逃げておきます。
@@ -444,7 +444,10 @@ def do_kwd(v, expr, info): do_expr( expr[3], info ) return None if v == 'for': - args = expr[2][2][::2] + args = expr[2][2] + is_omit = lambda lst, i: len(lst) <= i or lst[i] == [ 'etc', ';' ] + f = lambda lst, i: lst[:i] + [ [ 'num', '1' ] ] + lst[i:] if is_omit(lst, i) else lst + args = reduce( f, [ 0, 2, 4 ], args )[::2] do_expr( args[0], info ) while do_expr( args[1], info ): do_expr( expr[3], info ) 条件が省略されていたら「真」にする必要があるので、 数値の 1 を [ 'num', '1' ] として補間してみます。 他の箇所でも評価結果は参照されないので、 条件の箇所と同じく [ 'num', '1' ] を補間しておきます。
それでは実行。
$ cp esZ.py esa.py $ cat esa.py-diff.txt | patch $ ./esa.py for.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck5は正しく表示が出ましたが... 終了しません。 ^C で中断します。 ^CTraceback (most recent call last): File "./esa.py", line 570, inrcode = do_fcall( 'main', args, info ) File "./esa.py", line 536, in do_fcall do_blk(body, info) File "./esa.py", line 503, in do_blk do_expr(expr, info) File "./esa.py", line 469, in do_expr return do_kwd(v, expr, info) File "./esa.py", line 438, in do_kwd do_expr( expr[3], info ) File "./esa.py", line 481, in do_expr do_blk(expr, info) File "./esa.py", line 503, in do_blk do_expr(expr, info) File "./esa.py", line 469, in do_expr return do_kwd(v, expr, info) File "./esa.py", line 452, in do_kwd while do_expr( args[1], info ): File "./esa.py", line 471, in do_expr return float(v) KeyboardInterrupt for.c をみると if (lv >= ++lc) { v = 0; i = 1; for (;;) if (i<=10) v += i++; else break; show_res(lc, v); } 確かに。 キーワード break は、登録すらしてなかったので、 [ None, 'break' ] で何も処理されずに無限ループですね。
ループ中の break と continue の処理を追加してみます。
@@ -285,7 +285,7 @@ def es_split(s): 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void' ], - 'kwd' : [ 'return', 'if', 'else', 'while', 'for' ], + 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';' ], } kdic の 'kwd' グループに 'break', 'continue' を追加してます。 @@ -333,6 +333,19 @@ def get_call_inf(info): def get_env(info): return get_call_inf(info).get('env') +def get_flags(info): + return get_call_inf(info).get('flags') + +def loop_add(info, add): + get_call_inf(info)['loop'] += add + +def was_flags(info, f): + flags = get_flags(info) + ret = f in flags + if ret: + flags.remove(f) + return ret + def do_set(e, val, info): (k, v) = e[:2] if k == 'name': コールスタックのカレントの情報の辞書に、 キー 'flags', 'loop' を追加しているので、その操作用の関数を追加しています。 get_flags(info) フラグ群 flags (リストで実装) を返します。 was_flags(info, f) フラグ群 flags (リストで実装) に、f が設定されているかを返します。 f が設定されていた場合は、f をリストから取り除き、フラグをクリアします。 loop_add(info, add) ループのネスト数をカウントしているキー 'loop' について、 add を足し算します。 ループに入るところで +1 し、ループを出るところで -1 して使ってます。 @@ -440,18 +453,32 @@ def do_kwd(v, expr, info): do_expr( expr[4], info ) return None if v == 'while': + loop_add(info, 1) while do_expr( expr[2], info ): do_expr( expr[3], info ) + if was_flags(info, 'break'): + break + was_flags(info, 'continue') + loop_add(info, -1) return None while 文の処理への追加です。 コールスタックのカレントの情報のループネスト数のカウンタ 'loop' について、 処理の前後で +1, -1 してます。 フラグに 'break' が設定されていたら、フラグをクリア後ループを抜けます。 フラグに 'continue' が設定されてたら、フラグクリアだけします。 if v == 'for': args = expr[2][2] is_omit = lambda lst, i: len(lst) <= i or lst[i] == [ 'etc', ';' ] f = lambda lst, i: lst[:i] + [ [ 'num', '1' ] ] + lst[i:] if is_omit(lst, i) else lst args = reduce( f, [ 0, 2, 4 ], args )[::2] + + loop_add(info, 1) do_expr( args[0], info ) while do_expr( args[1], info ): do_expr( expr[3], info ) + if was_flags(info, 'break'): + break + was_flags(info, 'continue') do_expr( args[2], info ) + loop_add(info, -1) + return None for 文の処理への追加です。 while 文の場合と同様の内容です。 コールスタックのカレントの情報のループネスト数のカウンタ 'loop' について、 処理の前後で +1, -1 してます。 フラグに 'break' が設定されていたら、フラグをクリア後ループを抜けます。 フラグに 'continue' が設定されてたら、フラグクリアだけします。 + if v in [ 'break', 'continue' ]: + get_call_inf(info).get('flags').append(v) return None warn_no_sup('do_kwd', 'v', v) 関数 do_kwd() に、 [ 'kwd', 'break' ] と [ 'kwd', 'continue' ] の処理を追加しています。 コールスタックのカレントの情報のフラグに、 'break' あるいは 'continue' というフラグをセットします。 @@ -503,6 +530,9 @@ def do_blk(blk, info): do_expr(expr, info) if 'ret' in call_inf: return + flags = get_flags(info) + if 'break' in flags or 'continue' in flags: + return def get_sys_func(name): try: 関数 do_blk() でのブロック { ... } の実行処理に、 'break', 'continue' 時の処理を追加しています。 フラグに 'break' あるいは 'continue' がセットされたのを検出すると、 すみやかにブロック { ... } から抜けます。 抜けるだけで、フラグはノータッチです。 フラグのクリアは for 文あるいは while 文側の処理で行ないます。 @@ -532,7 +562,7 @@ def do_fcall(name, args, info): env = dict( zip(names, args) ) call_stk = info.get('call_stk') - call_stk.append( { 'name' : name, 'env' : env } ) + call_stk.append( { 'name' : name, 'env' : env, 'flags' : [], 'loop' : 0 } ) do_blk(body, info) call_inf = call_stk.pop() return call_inf.get('ret', None) do_fcall() のコールスタックの情報を生成する箇所に、 'flags' : [] 'loop' : 0 を追加しています。
それでは実行。
$ cp esa.py esb.py $ cat esb.py-diff.txt | patch $ ./esb.py for.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 $
for ループのなんやかんや、クリアです。
ここまですっかり忘れてました。グローバル変数。
使えるようにしたいですが、あと載せサクサクで上手く追加できるでしょうか。
ローカル変数や関数呼び出しの引数では、 実行のための情報をひっくるめた辞書 info 上にコールスタックを設け、 そのコールスタックのカレント位置の情報に env という名前の辞書を用意してました。
変数名を辞書 env のキーに、変数の値を辞書 env の値にしていました。
グローバル変数はスタックにする必要がないので、 辞書 info に genv という辞書を追加して使えば良さそうです。
変数を参照する箇所では、まずローカル変数の辞書 env に名前が無いか探し、 なければグローバル変数の辞書 genv を探すようにして、
変数への代入では、ローカル変数の辞書 env に名前があれば優先してそれを上書き。 無ければグローバル変数の辞書 genv を探し、見つかればグローバル変数の値を上書き。
genv にも無ければ、ローカル変数側に戻って env に新規追加すれば良さそうです。
素直に考えればこんなところでしょうか。 ですがちょっと考えを巡らせると、この段階で既に「試練」が思い当たります。
例えば int a = 1; というグローバル変数があったとします。 void foo(void) { int a = 2; : } void bar(void) { int b = a; : } 関数 foo で同じ名前のローカル変数を宣言した場合、 [ 'type', 'int' ], [ 'op', '=', [ 'name' 'a' ], [ 'num', '2' ] ] 現状では do_expr() で 'type' は無視するので、 実は、 void foo(void) { a = 2; : } と書かれている場合と同じ処理になります。 これってグローバル変数 a に代入しようとしてますよね。 その後、関数 bar でグローバル変数 a を参照したら、 関数 foo で代入した値が見えてしまいます。 ここまではローカル変数だけの世界だったので、 int a = xxx; の宣言は a = xxx; の代入として扱っても、env にキー a が追加されて上手くいってました。 また、 int a; の宣言は a; の参照として扱っていて、 env にキー a が無ければ python の値 None が返ってきます。 ですがその None はどこにも使用されず、問題にはなりませんでした。
グローバル変数を追加する前に、この手抜きを直しておかねばややこしくなります。
諸悪の根元は、宣言の type を無視しても、代入で辞書にキーが存在してなくても、 何とかなってしまっている事でしょう。
まずはローカル変数だけの世界で [ 'type', ... ]
の時だけ、
辞書 env にキーを追加するように変更してみます
方針
例えば unsigned char **p; は [ 'type', 'unsigned', 'char', '*', '*' ], [ 'name', 'p' ], [ 'etc', ';' ], と保持してました。 名前 p を type の情報として入れ子にしたいところですが、 現状はベタなリストでは位置が確定しないので、まず [ 'type', [ 'unsigned', 'char', '*', '*' ] ], [ 'name', 'p' ], [ 'etc', ';' ], にまとめてから、 [ 'type', [ 'unsigned', 'char', '*', '*' ], [ 'name', 'p' ] ], [ 'etc', ';' ], にもっていきます。 関数定義の引数の箇所はどうすべきでしょうか? 例えば void foo(int a, char *b) {} 従来は - - fdef - foo - - br_s - ( - - [type, int] - [name, a] - [op, ','] - [type, char, '*'] - [name, b] - [type, void] - - br_s - '{' - [] の並びを期待して、引数名のリストを抽出してました。 (関数定義の引数箇所ではコンマ演算子 ',' のまとめは非適用です) type 直後の name を入れ子にすると、 - - fdef - foo - - br_s - ( - - - type - [int] - [name, a] - [op, ','] - - type - - [char, '*'] - [name, b] 引数名のリストを抽出する箇所を変更せねばなりません。 ここはひとつ、関数定義の引数箇所では [ type, char, * ] を [ type, [ char, * ] ] にするだけに留めておいて - - fdef - foo - - br_s - ( - - - type - [int] - [name, a] - [op, ','] - - type - - [char, '*'] - [name, b] の形式にしましょうか。
@@ -67,12 +67,13 @@ def join_type(top, lst): i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) return lst if i is None else lst[:i] + join_type( lst[i], lst[i+1:] ) i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' or e == [ 'op', '*' ] ), None ) + to_len2 = lambda e: [ e[0], e[1:] ] if i is None: - return [ top ] + lst + return [ to_len2(top) ] + lst if i > 0: - return [ top ] + lst[:i] + join_type( None, lst[i:] ) - top += lst[0][1] - return join_type( top, lst[1:] ) + return [ to_len2(top) ] + lst[:i] + join_type( None, lst[i:] ) + top += [ lst[0][1] ] + return join_type( to_len2(top), lst[1:] ) def tree_bra(lst, kdic, sta=None): d = dict( zip( kdic.get('br_s'), kdic.get('br_e') ) ) join_type() の段階で、 [ type, unsigned, char, *, * ] じゃなくて [ type, [ unsigned, char, *, * ] ] にまとめておくように変更してます。 @@ -132,15 +133,13 @@ def ops_idx(ops, term, op): op = op[0] i = next( ( i for (i, (term_, bind, lst)) in enumerate(ops) if term_ == term and op in lst ), None ) if i is None: - print yaml.dump(tstk) - err_exit("not found term={} op='{}' in ops".format(term, op)) + err_not_found("term={} op='{}' in ops".format(term, op)) return i エラーメッセージ処理関連の変更です。 def tree_op1(lst, ops, k, v): r = tree_op( lst[1:], ops ) if len(r) == 0: - print yaml.dump(tstk) - err_exit("not found rignt term op='{}'".format(v)) + err_not_found("rignt term op='{}'".format(v)) (k2, v2) = r[0][:2] if k2 == 'op': i1 = ops_idx(ops, 1, v) エラーメッセージ処理関連の変更です。 @@ -162,8 +161,7 @@ def op2(ops, e, k1, v1, r0): def tree_op2(lst, ops, e, k1, v1): r = tree_op( lst[2:], ops ) if len(r) == 0: - print yaml.dump(tstk) - err_exit("not found right term op='{}'".format(v1)) + err_not_found("right term op='{}'".format(v1)) return [ op2( ops, e, k1, v1, r[0] ) ] + r[1:] エラーメッセージ処理関連の変更です。 @@ -215,7 +213,7 @@ def tree_kwd(lst, kdic): return [ [ k, v, r[0] ] ] + r[1:] if v == 'if' and len( lst[2:] ) > 0: if lst[1][:2] != [ 'br_s', '(' ]: - err_exit("not found '(' after 'if'") + err_not_found("'(' after 'if'") r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] if top_is(r, 'etc', ';'): エラーメッセージ処理関連の変更です。 @@ -225,13 +223,30 @@ def tree_kwd(lst, kdic): return [ e ] + r if v in [ 'while', 'for' ] and len( lst[2:] ) > 0: if lst[1][:2] != [ 'br_s', '(' ]: - err_exit("not found '(' after '{}'".format(v)) + err_not_found("'(' after '{}'".format(v)) r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] return [ e ] + r e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) エラーメッセージ処理関連の変更です。 +def tree_type(lst): + if len(lst) == 0: + return lst + e = lst[0] + step = lambda e, i: [ e ] + tree_type( lst[i:] ) + if e[0] == 'br_s': + e = [ e[0], e[1], tree_type( e[2] ) ] + return step(e, 1) + if e[0] == 'fdef': + e4 = tree_type( [ e[4] ] )[0] + e = [ e[0], e[1], e[2], e[3], e4 ] + return step(e, 1) + if e[0] == 'type' and len( lst[1:] ) > 0: + e = [ e[0], e[1], lst[1] ] + return step(e, 2) + return step(e, 1) + def es_split(s): s = s.replace('@', '@ ') @@ -325,6 +340,8 @@ def es_split(s): lst = tree_kwd(lst, kdic) + lst = tree_type(lst) + return lst def get_call_inf(info): (ようやくここから) es_split() の末尾に tree_type() の呼び出しを追加して、 [ 'type', [ ... ] ], xxx な箇所のうち、変換が必要な箇所について [ 'type', [ ... ], xxx ] の入れ子にしています。 @@ -346,17 +363,35 @@ def was_flags(info, f): flags.remove(f) return ret +def is_exist(name, info): + return name in get_env(info) + +def get_val(name, info): + if is_exist(name, info): + return get_env(info).get(name) + err_not_found(name) + +def set_val(name, val, info): + if is_exist(name, info): + get_env(info)[name] = val + return val + err_not_found(name) + +def new_val(name, val, info): + if not is_exist(name, info): + get_env(info)[name] = val + return val + err_exit("alredy exist '{}'".format(name)) + コールスタック上の変数関連の操作をする ユーティリティ関数を追加してます。 is_exit() : 変数が存在する判定します。 get_val() : 変数の値を返します。 存在しなければエラー set_val() : 変数に値を設定します。 存在しなければエラー new_val() : 変数を生成します。 既に存在していればエラー def do_set(e, val, info): (k, v) = e[:2] if k == 'name': - get_env(info)[v] = val + set_val(v, val, info) return val if k == 'arr': idx = e[2] i = int( do_expr(idx, info) ) arr = get_arr_with_chk(v, i, info) - if arr is None: - err_exit("not found arr '{}'".format(name)) arr[i] = val return val 変数操作用のユーティリティ関数を使うように変更しています。 重複するエラー処理を削除しています。 @@ -428,18 +463,15 @@ def do_op(v, args, info): warn_no_sup('do_op', 'term', term) def get_arr_with_chk(name, i, info): - arr = get_env(info).get(name) - if arr is not None and i >= len(arr): + arr = get_val(name, info) + if i >= len(arr): err_exit('over acc {}[{}]'.format(name, i)) return arr 変数操作用のユーティリティ関数を使うように変更しています。 def do_arr(name, idx, info): i = int( do_expr(idx, info) ) arr = get_arr_with_chk(name, i, info) - if arr is not None: - return arr[i] - get_env(info)[name] = [ None ] * i # new - return None + return arr[i] 本来は参照時の処理ですが、 「存在しなければ生成する」ごまかしを入れてました。 ごまかしは削除して、本来の参照だけの処理に変更しています。 def do_kwd(v, expr, info): if v == 'return': @@ -478,15 +510,34 @@ def do_kwd(v, expr, info): loop_add(info, -1) return None if v in [ 'break', 'continue' ]: - get_call_inf(info).get('flags').append(v) + get_flags(info).append(v) return None warn_no_sup('do_kwd', 'v', v) 以前に追加したユーティリティ関数へ置き換え漏れです。変更しておきます。 +def flat_comma(e): + if e[:2] != [ 'op', ',' ]: + return [ e ] + return flat_comma( e[2] ) + [ e[3] ] + def do_comma(e, info): - if e[:2] != ['op', ',']: - return [ do_expr(e, info) ] - return do_comma( e[2], info ) + [ do_expr( e[3], info ) ] + lst = flat_comma(e) + return [ do_expr(e, info) for e in lst ] 引数箇所の ',' コンマ演算子による入れ子のツリーについて 一旦フラットなリストに戻す関数 flat_comma() を追加しています。 関数呼び出しで引数のツリーを評価してフラットな結果のリストを返す関数 do_comma() でも、flat_comma() を使って先にフラットなリストにしてから、 評価するように変更しています。 + +def do_type(expr, info): + for e in flat_comma( expr[2] ): + val = None + if e[:2] == [ 'op', '=' ]: + val = do_expr( e[3], info ) + e = e[2] + if e[0] == 'arr': + idx = e[2] + n = int( do_expr(idx, info) ) + if val is None: + val = [ None ] * n + name = e[1] + new_val(name, val, info) + return None def do_expr(expr, info): if info.get('verb') >= 2: ここまで do_expr() で無視してた [ 'type', ... ] を処理するため 関数 do_type() を追加しています。 配列の初期化は未対応です。 配列の初期化を指定していても、 しれっと無視して [ None, None, ... ] の値になるはずです。 @@ -499,7 +550,7 @@ def do_expr(expr, info): if k == 'str': return expr[2] if k == 'name': - return get_env(info).get(v) + return get_val(v, info) if k == 'br_s': if v in [ '(', '[' ]: # '[' for do_arr() e = expr[2][0] 変数操作用のユーティリティ関数を使うように変更しています。 @@ -516,8 +567,10 @@ def do_expr(expr, info): if len(args) > 0: args = do_comma( args[0], info ) return do_fcall(v, args, info) + if k == 'type': + return do_type(expr, info) - if k in [ 'type', 'fproto', 'etc' ]: # do nothing !!! + if k in [ 'fproto', 'etc' ]: # do nothing !!! return None warn_no_sup('do_expr', '[k, v]', [k, v]) do_expr() で無視してた 'type' について、 do_type() を呼び出すように変更しています。 @@ -525,7 +578,7 @@ def do_expr(expr, info): def do_blk(blk, info): (_, _, lst) = blk # [ 'br_s', '{', lst ] - call_inf = info.get('call_stk')[-1] + call_inf = get_call_inf(info) for expr in lst: do_expr(expr, info) if 'ret' in call_inf: 以前に追加したユーティリティ関数へ置き換え漏れです。変更しておきます。 @@ -550,11 +603,10 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: - v = get_env(info).get(name) - func = v if v else get_sys_func(name) + func = get_val(name, info) if is_exist(name, info) else get_sys_func(name) if func: return func(*args) - err_exit("not found fdef '{}'".format(name)) + err_not_found("fdef '{}'".format(name)) (args_info, ret_type, body) = fdef[2:] (_, _, args_lst) = args_info 変数操作用のユーティリティ関数を使うように変更しています。 エラーメッセージ処理関連の変更です。 @@ -573,30 +625,45 @@ def warn_no_sup(f, t, v): def warn(msg): print('Warn {}'.format(msg)) +def err_not_found(name): + err_exit("not found {}".format(name)) + +ginfo = {} + +def show_yaml(v, title): + print('--- {} ---'.format(title)) + print( yaml.dump(v) ) + def err_exit(msg, rcode=1): + if tstk: + show_yaml(tstk, 'tree stack') + if ginfo: + if ginfo.get('verb') < 2: + ginfo['lst'] = '...' + show_yaml(ginfo, 'info') print('Err {}'.format(msg)) sys.exit(rcode) エラーメッセージ処理関連の変更です。 if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: - print('usage: {} [-v -vv] file'.format(sys.argv[0])) + print('usage: {} [-v -vv -vvv] file'.format(sys.argv[0])) else: verb = 0 argv = sys.argv[:] - for (i, opt) in enumerate( [ '-v', '-vv' ] ): + for (i, opt) in enumerate( [ '-v', '-vv', '-vvv' ] ): if opt in argv: argv.remove(opt) verb = i + 1 with open(argv[1], 'r') as f: s = f.read() lst = es_split(s) - if verb >= 1: - print yaml.dump(lst) - + if verb >= 3: + show_yaml(lst, 'lst') argv = argv[1:] args = [ len(argv), argv ] info = { 'lst' : lst, 'call_stk' : [], 'verb' : verb } + ginfo = info rcode = do_fcall( 'main', args, info ) rcode = int(rcode) sys.exit(rcode) エラーメッセージ処理関連の変更です。
まずは簡単なソースで確認をば。
$ cat type.c int main(int ac, char **av) { int a, b = 1; return 0; } $ まず以前の状態を確認します。 $ ./esb.py -v type.c - - fdef - main - - br_s - ( - - [type, int] - [name, ac] - [op, ','] - [type, char, '*', '*'] - [name, av] - [type, int] - - br_s - '{' - - [type, int] - - op - ',' - [name, a] - - op - '=' - [name, b] - [num, '1'] - [etc, ;] - - kwd - return - [num, '0'] - [etc, ;] do_fcall() name=main args=[1, ['type.c']] $ 更新してみますると $ cp esb.py esc.py $ cat esc.py-diff.txt | patch $ ./esc.py -vvv type.c --- lst --- - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [int] - - op - ',' - [name, a] - - op - '=' - [name, b] - [num, '1'] - [etc, ;] - - kwd - return - [num, '0'] - [etc, ;] do_fcall() name=main args=[1, ['type.c']] expr=['type', ['int'], ['op', ',', ['name', 'a'], ['op', '=', ['name', 'b'], ['num', '1']]]] expr=['num', '1'] expr=['etc', ';'] expr=['kwd', 'return', ['num', '0']] expr=['num', '0'] $ 今回から -vvv にしないとリストは出ません。 関数定義の引数箇所は、例えば - - [type, int] - [name, ac] から - - - type - [int] - [name, ac] に。 変数宣言の箇所は、例えば - - [type, int] - - op - ',' - [name, a] - - op - '=' - [name, b] - [num, '1'] - [etc, ;] から - - - type - [int] - - op - ',' - [name, a] - - op - '=' - [name, b] - [num, '1'] - [etc, ;] に。
それでは実行。
と言ってもあまり意味のありそうなソースが無いですが $ ./esc.py uhyo.c $ echo $? 18 $ $ ./esc.py a10.c $ echo $? 55 $ $ ./esc.py for.c --- info --- call_stk: - env: argc: 1 argv: [for.c] i: 11.0 lc: 1.0 lv: 100.0 v: 55.0 flags: [] loop: 0 name: main - env: {lv: 1.0, v: 55.0} flags: [] loop: 0 name: show_res - env: {s: ck} flags: [] loop: 0 name: show_str lst: '...' verb: 0 Err not found f $ うう。 show_str() で f が見つからないと...。 void show_str(char *s) { //|--> printf("%s", s); //<--| /*-->| f = eval("sys.stdout.write"); f(s); |<--*/ } ああ、どうせインタプリタでしか実行されない箇所と思って、 変数宣言せずに、いきなり代入してました。 さて f の型はどうすべきか? 普通は void (*f)(char *) とかなのでしょうが、 そんなややこしいのが、まともに通るハズもなく。 そして実際にも、代入する値は python の関数オブジェクトだし。
とりあえず、型が存在すれば変数宣言と思うので、 ありさえすれば何でもいいです。
$ diff -up -L before -L after for.c for2.c --- before +++ after @@ -8,7 +8,7 @@ void show_str(char *s) //<--| /*-->| - f = eval("sys.stdout.write"); + void *f = eval("sys.stdout.write"); f(s); |<--*/ } @@ -20,7 +20,7 @@ void show_int(int v) //<--| /*-->| - f = eval("int") + void *f = eval("int") show_str( str( f(v) ) ); |<--*/ } $ なんでもいいので void * にしてみました。 $ ./esc.py for2.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 $
さて本題のグローバル変数。
@@ -347,8 +347,8 @@ def es_split(s): def get_call_inf(info): return info.get('call_stk')[-1] -def get_env(info): - return get_call_inf(info).get('env') +def get_env(info, g=False): + return info.get('genv') if g else get_call_inf(info).get('env') def get_flags(info): return get_call_inf(info).get('flags') 変数用の辞書 env を取得する関数を改造しています。 引数 g=True を指定すると、グローバル変数用の辞書を返します。 @@ -363,22 +363,29 @@ def was_flags(info, f): flags.remove(f) return ret -def is_exist(name, info): - return name in get_env(info) +def is_exist(name, info, g=None): + if g is not None: + return name in get_env(info, g) + return is_exist(name, info, g=False) or is_exist(name, info, g=True) 変数用の辞書の指定の変数名が存在するか判定します。 引数 g=True を指定すると、グローバル変数用の辞書に存在するか判定します。 引数 g=False を指定すると、ローカル変数用の辞書に存在するか判定します。 引数 g の指定を省略 をすると、どちらかの辞書に存在するか判定します。 def get_val(name, info): - if is_exist(name, info): + if is_exist(name, info, g=False): return get_env(info).get(name) + if is_exist(name, info, g=True): + return get_env(info, g=True).get(name) err_not_found(name) def set_val(name, val, info): - if is_exist(name, info): + if is_exist(name, info, g=False): get_env(info)[name] = val return val + if is_exist(name, info, g=True): + get_env(info, g=True)[name] = val + return val err_not_found(name) まずローカル変数用の辞書に存在するか試し、 なければグローバル変数用の辞書で試すように変更してます。 def new_val(name, val, info): - if not is_exist(name, info): + if not is_exist(name, info, g=False): get_env(info)[name] = val return val err_exit("alredy exist '{}'".format(name)) この呼び出しで動的に変数を生成するのはローカル変数の場合だけなので、 ローカル変数の辞書のみチェックするようにしています。 @@ -570,7 +577,7 @@ def do_expr(expr, info): if k == 'type': return do_type(expr, info) - if k in [ 'fproto', 'etc' ]: # do nothing !!! + if k in [ 'fproto', 'etc', 'fdef' ]: # do nothing !!! return None warn_no_sup('do_expr', '[k, v]', [k, v]) main 関数を実行する前に、グローバル変数を生成する処理を追加しているので、 そのときに [ 'fdef', xxx, ... ] のデータも do_expr() に渡るようになりました。 関数定義 fdef で何も実行しないように登録を追加しています。 @@ -619,6 +626,16 @@ def do_fcall(name, args, info): call_inf = call_stk.pop() return call_inf.get('ret', None) +def do_global(lst, verb): + genv = {} + inf = { 'name' : None, 'env' : genv, 'flags' : [], 'loop' : 0 } + call_stk = [ inf ] + info = { 'lst' : lst, 'call_stk' : call_stk, 'genv' : genv, 'verb' : verb } + blk = [ 'br_s', '{', lst ] + do_expr(blk, info) + info.get('call_stk').pop() + return info + def warn_no_sup(f, t, v): warn("{}() not support {}='{}'".format(f, t, v)) main 関数を実行する前に行なう、グローバル変数用の前処理として 関数 do_global() を追加してます。 es_split() で構築したリストをなめて、 グローバル変数の辞書を含む、 その他、実行のための情報をひっくるめた辞書 info を返します。 実際の処理では es_split() で構築したリストを { ... } で囲み、 ブロックの構造にして、do_expr() を経由して do_blk() を呼び出します。 その際、予めコールスタックに、1つだけ情報を積んでから実行します。 グローバル変数宣言箇所は do_blk() からの実行で、 ローカル変数としてコールスタック上に生成されて返ります。 コールスタック上に生成されたローカル変数用の辞書を、 main 関数を実行するときのグローバル変数用の辞書として配置しなおしています。 @@ -660,9 +677,11 @@ if __name__ == "__main__": lst = es_split(s) if verb >= 3: show_yaml(lst, 'lst') + + info = do_global(lst, verb) + argv = argv[1:] args = [ len(argv), argv ] - info = { 'lst' : lst, 'call_stk' : [], 'verb' : verb } ginfo = info rcode = do_fcall( 'main', args, info ) rcode = int(rcode) その他、実行のための情報をひっくるめた辞書 info は、 do_global() を呼び出して取得するように変更してます。
まずは簡単なソースで確認をば。
$ cp esc.py esd.py $ cat esd.py-diff.txt | ptach $ cat global.c int a = 123; int foo(void) { return a; } int main(int ac, char **av) { int a = 1; if (ac > 1) return foo(); return a; } $ 引数なしで実行すると、ローカル変数の値を返し、 なにかコマンド引数をつけて実行すると、グローバル変数の値を返すはずです。 $ ./esd.py global.c ; echo $? 1 $ $ ./esd.py global.c g ; echo $? 123 $ 従来のローカル変数だけのプログラムも走らせてみて。 $ ./esd.py for2.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 $ 大丈夫そうですね。
sin 関数を使えるようにしてみましょう。 例えば
$ cat sin.c #include <stdio.h> #include <math.h> /*-->| only interpreter void *stdout_write = eval("sys.stdout.write") void *stdout_flush = eval("sys.stdout.flush") |<--*/ // only interpreter //|--> only compiler void stdout_write(char *s) { printf("%s", s); } void stdout_flush(void) { fflush(stdout); } //<--| // only compiler void show_str(char *s) { stdout_write(s); stdout_flush(); } void show_spc(int n) { int i; for (i=0; i<n; i++) show_str(" "); } int main(int ac, char **av) { double r = 10.0; int i, n = 20; for (i=0; i<n; i++) { double a = 2 * M_PI * i / n; int m = sin(a) * r + r; show_spc(m); show_str("@\n"); } return 0; } $ コンパイラで実行すると $ gcc -o sin sin.c -lm $ ./sin @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ そのままインタプリタで試すと $ ./esd.py sin.c --- info --- call_stk: - env: ac: 1 av: [sin.c] flags: [] loop: 0 name: main genv: {stdout_flush: !!python/name:None.flush '', stdout_write: !!python/name:None.write ''} lst: '...' verb: 0 Err not found double $
確かに! 'double' を予約語として登録してませんでした。
@@ -299,7 +299,7 @@ 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, - 'type' : [ 'int', 'char', 'void' ], + 'type' : [ 'int', 'char', 'void', 'double' ], 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';' ], 'double' を登録追加です。
それでは実行。
$ cp esd.py ese.py $ cat ese.py-diff.txt | patch $ ./ese.py sin.c Warn do_op2() not support v='.' Traceback (most recent call last): File "./ese.py", line 686, inrcode = do_fcall( 'main', args, info ) File "./ese.py", line 625, in do_fcall do_blk(body, info) File "./ese.py", line 590, in do_blk do_expr(expr, info) File "./ese.py", line 554, in do_expr return do_kwd(v, expr, info) File "./ese.py", line 512, in do_kwd do_expr( expr[3], info ) File "./ese.py", line 566, in do_expr do_blk(expr, info) File "./ese.py", line 590, in do_blk do_expr(expr, info) File "./ese.py", line 578, in do_expr return do_type(expr, info) File "./ese.py", line 535, in do_type for e in flat_comma( expr[2] ): IndexError: list index out of range $ ん? Warn do_op2() not support v='.' 構造体のメンバアクセス用の演算子 '.' ? int main(int ac, char **av) { double r = 10.0; int i, n = 20; : ここでは! 10.0 を 10 . 0 で 変数 '10' のメンバ '0' と思ったの? $ ./ese.py -vvv sin.c --- lst --- - - fdef - show_str : - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [double] - - op - '=' - [name, r] - - op - . - [num, '10'] - [num, '0'] - [etc, ;] : みたいです。 - - op - . - [num, '10'] - [num, '0'] 演算子 '.' と小数点 '.' どうすべしか...。 数値なら '.' の直後に '0'から'9' のどれかが必ずくるはずですよね。 となると... '.0' から '.9' までを単語として登録しておけば、 長い単語からエンコードするので '.' よりも前に処理されます。 でもデコード処理で名前の条件を満たしてないので、 前後にスペースが入ってしまいますね。 10.0; --> 10@xxyy; --> 10 .0 ; ここは、少しズルい事をしてみます。(こんなのばっかり) 必ずスペースを追加しないで元通りに戻す「グループ」を、 kdic に追加して対応してみます。
def decode(s, tbl, add_spc=False): - d = lambda d1: ' ' + d1 + ' ' if add_spc and not is_name(d1) else d1 - f = lambda t, (k, s1, d1, ec): t.replace( ec, d(d1) ) + add = lambda d1, k: add_spc and not is_name(d1) and k != 'no_spc' + d = lambda d1, k: ' ' + d1 + ' ' if add(d1, k) else d1 + f = lambda t, (k, s1, d1, ec): t.replace( ec, d(d1, k) ) return reduce(f, tbl, s).replace('@ ', '@') def fidx(s, lst): デコードで 'no_spc' というグループなら、 無条件でスペースは追加しないように変更しています。 @@ -303,6 +304,7 @@ def es_split(s): 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';' ], + 'no_spc': [ '.0', '.1', '.2', '.3', '.4', '.5', '.6', '.7', '.8', '.9' ], } enc = lambda ki, idx: '@{:02d}{:02d}'.format(ki, idx) そして kdic に 'no_spc' というグループとして、 '.0' から '.9' をずらずらっと登録しています。
それでは実行。
$ cp ese.py esf.py $ cat esf.py-diff.txt | patch $ ./esf.py sin.c Traceback (most recent call last): File "./esf.py", line 688, inrcode = do_fcall( 'main', args, info ) File "./esf.py", line 627, in do_fcall do_blk(body, info) File "./esf.py", line 592, in do_blk do_expr(expr, info) File "./esf.py", line 556, in do_expr return do_kwd(v, expr, info) File "./esf.py", line 514, in do_kwd do_expr( expr[3], info ) File "./esf.py", line 568, in do_expr do_blk(expr, info) File "./esf.py", line 592, in do_blk do_expr(expr, info) File "./esf.py", line 580, in do_expr return do_type(expr, info) File "./esf.py", line 537, in do_type for e in flat_comma( expr[2] ): IndexError: list index out of range $ なにやら list の添字のエラー $ ./esf.py -vvv sin.c --- lst --- - - type - - [void, '*'] : - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [double] - - op - '=' - [name, r] - [num, '10.0'] - [etc, ;] : 例の箇所自体は - [num, '10.0'] で、数値として認識されてますね。 $ ./esf.py -vv sin.c | fold : expr=['name', 'n'] expr=['br_s', '{', [['type', ['double']], ['op', '=', ['name', 'a'], ['op', '/', ['op', '*', ['op', '*', ['num', '2'], ['name', 'M_PI']], ['name', 'i']], ['name ', 'n']]], ['etc', ';'], ['type', ['int']], ['op', '=', ['name', 'm'], ['op', '+ ', ['op', '*', ['fcall', 'sin', ['br_s', '(', [['name', 'a']]]], ['name', 'r']], ['name', 'r']]], ['etc', ';'], ['fcall', 'show_spc', ['br_s', '(', [['name', 'm ']]]], ['etc', ';'], ['fcall', 'show_str', ['br_s', '(', [['str', '"', '@\n']]]] , ['etc', ';']]] expr=['type', ['double']] rcode = do_fcall( 'main', args, info ) File "./esf.py", line 627, in do_fcall do_blk(body, info) File "./esf.py", line 592, in do_blk do_expr(expr, info) File "./esf.py", line 556, in do_expr return do_kwd(v, expr, info) File "./esf.py", line 514, in do_kwd do_expr( expr[3], info ) File "./esf.py", line 568, in do_expr do_blk(expr, info) File "./esf.py", line 592, in do_blk do_expr(expr, info) File "./esf.py", line 580, in do_expr return do_type(expr, info) File "./esf.py", line 537, in do_type for e in flat_comma( expr[2] ): IndexError: list index out of range どうも expr=['type', ['double']] について expr[2] をとろうとしてる? [['type', ['double']], ['op', '=', ['name', 'a'] ... なので main() の for 文のブロックの中。 : for (i=0; i<n; i++) { double a = 2 * M_PI * i / n; : -vvv でみて - - kwd - for - - br_s - ( - - - op - '=' : - - br_s - '{' - - - type - [double] - - op - '=' - [name, a] - - op - / - - op - '*' : ああ、[ type, [ dobule ] ] 直後の [ op, =, ... ] が type の中に入ってないか...。 : int main(int ac, char **av) { double r = 10.0; : - - - type - [double] - - op - '=' - [name, r] - [num, '10.0'] でもこっちは type の中に入ってます。 はて? def tree_type(lst): if len(lst) == 0: return lst e = lst[0] step = lambda e, i: [ e ] + tree_type( lst[i:] ) if e[0] == 'br_s': e = [ e[0], e[1], tree_type( e[2] ) ] return step(e, 1) if e[0] == 'fdef': e4 = tree_type( [ e[4] ] )[0] e = [ e[0], e[1], e[2], e[3], e4 ] return step(e, 1) if e[0] == 'type' and len( lst[1:] ) > 0: e = [ e[0], e[1], lst[1] ] return step(e, 2) return step(e, 1) ああなるほど。 予約後 'if', 'while', 'for' の { ... } ブロックは tree_kwd() で入れ子にしてました。 - - kwd - for - - br_s - ( - - - op - '=' - [name, i] - [num, '0'] - [etc, ;] - - op - < - [name, i] - [name, n] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i] - - br_s - '{' [ 'kwd', 'for', [ 'br_s', '(' ... ], [ 'br_s', '{' ... ] ] 'fdef' の場合と同様に tree_type() で対応しないと、 'for' をみつけても step(e, 1) にきて、 for (...) { ... } までは e に入ってて、そのままになってました。
@@ -243,6 +243,10 @@ def tree_type(lst): e4 = tree_type( [ e[4] ] )[0] e = [ e[0], e[1], e[2], e[3], e4 ] return step(e, 1) + if e[0] == 'kwd' and e[1] in [ 'if', 'while', 'for' ]: + try_blk = lambda t: tree_type( [ t ] )[0] if t[:2] == [ 'br_s', '{' ] else t + e = e[:2] + [ try_blk(t) for t in e[2:] ] + return step(e, 1) if e[0] == 'type' and len( lst[1:] ) > 0: e = [ e[0], e[1], lst[1] ] return step(e, 2) tree_type() に if, while, for 用の処理を追加してます。
それでは実行。
$ cp esf.py esg.py $ cat esg.py-diff.txt | patch $ ./esg.py -vvv sin.c --- lst --- - - type - - [void, '*'] - - op : - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [double] - - op - '=' - [name, r] - [num, '10.0'] - [etc, ;] : - - kwd - for - - br_s - ( - - - op - '=' - [name, i] - [num, '0'] - [etc, ;] - - op - < - [name, i] - [name, n] - [etc, ;] - - op - !!python/tuple [++, back] - [name, i] - - br_s - '{' - - - type - [double] - - op - '=' - [name, a] - - op - / - - op - '*' - - op - '*' - [num, '2'] - [name, M_PI] - [name, i] - [name, n] - [etc, ;] よしよし。 $ ./esg.py sin.c --- info --- call_stk: - env: ac: 1 av: [sin.c] i: 0.0 n: 20.0 r: 10.0 flags: [] loop: 1 name: main genv: {stdout_flush: !!python/name:None.flush '', stdout_write: !!python/name:None.write ''} lst: '...' verb: 0 Err not found M_PI $ で、次はそうか。そら無いわな。
@@ -3,6 +3,9 @@ import sys import yaml +def sys_exec(s): + exec s + def is_num(s): try: float(s) eval に続く「茶ぶ台返し」です。 もう、何でもアリです。
$ diff -up -L before -L after sin.c sin2.c --- before +++ after @@ -6,6 +6,10 @@ void *stdout_write = eval("sys.stdout.write") void *stdout_flush = eval("sys.stdout.flush") +sys_exec("global math ; import math") +double M_PI = eval("math.pi"); +void *sin = eval("math.sin"); + |<--*/ // only interpreter //|--> only compiler $ グローバル変数用の処理でリスト全体をなめるときに、 python の処理系側の sys_exec() を呼び出して、 python 側で "import math" を走らせます。 python 側で "math.pi" の評価結果を、グローバル変数 M_PI にセット。 python 側の "math.sin" 関数オブジェクトを、グローバル変数 sin にセット。 「これがアリなら、なんとでもなるじゃん」的な気がしますが...
それでは実行。
$ cp esg.py esh.py $ cat esh.py-diff.txt | patch $ ./esh.py sin2.c @ --- info --- call_stk: - env: a: 0.0 ac: 1 av: [sin2.c] i: 1.0 m: 10.0 n: 20.0 r: 10.0 flags: [] loop: 1 name: main genv: {M_PI: 3.141592653589793, sin: !!python/name:math.sin '', stdout_flush: !!python/name:None.flush '', stdout_write: !!python/name:None.write ''} lst: '...' verb: 0 Err alredy exist 'a' $ なんか '@' がちらっとででエラーです。 int main(int ac, char **av) { double r = 10.0; int i, n = 20; for (i=0; i<n; i++) { double a = 2 * M_PI * i / n; int m = sin(a) * r + r; show_spc(m); show_str("@\n"); } return 0; } そうかー。 関数呼び出しではコールスタックに情報を積んでますが、 for 文のブロックでは、コールスタックはそのままです。 だからこそローカル変数の r にはアクセスできてるのですが、 for 文の繰り返しでブロック先頭にきて、 もっかい変数 a を生成しようとしたら、 初回のループで既に作ってたのでエラーと...。
for 文などのブロックでコールスタックを積むとなると、 前のが隠れるのでそれを見えるようにせねばなりません。
でもそうすると、関数の呼び元の変数も見えてしまいます。
ならば、ならば、見つかるまでスタックを遡っていくとして、 関数呼び出しのスタックまできたら、そこで打ち止めにしましょうか。
まずは前準備として関連のコードを整理してみます。
@@ -360,10 +360,7 @@ def get_env(info, g=False): return info.get('genv') if g else get_call_inf(info).get('env') def get_flags(info): - return get_call_inf(info).get('flags') - -def loop_add(info, add): - get_call_inf(info)['loop'] += add + return info.get('flags') def was_flags(info, f): flags = get_flags(info) コールスタック上に「ループのネスト回数」を記録する 'loop' を用意してましたが、 カウントを更新するだけで、結局どこからも参照してませんでした。 エラーチェックに使うべきでしょうが、まぁ無くても良しということで、 一旦削除しておきます。 また、コールスタック上に 'flags' というリストを用意して、 'break' や 'continue' をセット/クリアしてました。 コールスタック上に設けなくてもよさそうなので、 なんやかんやの辞書 info 上に移動しています。 @@ -492,7 +489,7 @@ def do_arr(name, idx, info): def do_kwd(v, expr, info): if v == 'return': r = do_expr( expr[2], info ) - get_call_inf(info)['ret'] = r + info['ret'] = r return None if v == 'if': if do_expr( expr[2], info ): return 文での返り値を、コールスタック上の 'ret' に記録して、 関数の呼び元で参照してました。 コールスタックにせずとも、なんやかんやの辞書 info に 直接入れても問題ないでしょう。 ということで info 上に変更してます。 @@ -501,13 +498,11 @@ def do_kwd(v, expr, info): do_expr( expr[4], info ) return None if v == 'while': - loop_add(info, 1) while do_expr( expr[2], info ): do_expr( expr[3], info ) if was_flags(info, 'break'): break was_flags(info, 'continue') - loop_add(info, -1) return None if v == 'for': args = expr[2][2] 'loop' によるネスト回数のカウント処理削除です。 @@ -515,7 +510,6 @@ def do_kwd(v, expr, info): f = lambda lst, i: lst[:i] + [ [ 'num', '1' ] ] + lst[i:] if is_omit(lst, i) else lst args = reduce( f, [ 0, 2, 4 ], args )[::2] - loop_add(info, 1) do_expr( args[0], info ) while do_expr( args[1], info ): do_expr( expr[3], info ) @@ -523,7 +517,6 @@ def do_kwd(v, expr, info): break was_flags(info, 'continue') do_expr( args[2], info ) - loop_add(info, -1) return None if v in [ 'break', 'continue' ]: get_flags(info).append(v) 'loop' によるネスト回数のカウント処理削除です。 @@ -597,7 +590,7 @@ def do_blk(blk, info): call_inf = get_call_inf(info) for expr in lst: do_expr(expr, info) - if 'ret' in call_inf: + if 'ret' in info: return flags = get_flags(info) if 'break' in flags or 'continue' in flags: 'ret' は コールスタックから 'info' に移動しました。 @@ -630,16 +623,20 @@ def do_fcall(name, args, info): env = dict( zip(names, args) ) call_stk = info.get('call_stk') - call_stk.append( { 'name' : name, 'env' : env, 'flags' : [], 'loop' : 0 } ) + call_stk.append( { 'name' : name, 'env' : env } ) do_blk(body, info) call_inf = call_stk.pop() - return call_inf.get('ret', None) + + was_flags(info, 'break') + was_flags(info, 'continue') + ret = info.pop('ret', None) + return ret 関数呼び出し処理 do_fcall() で積むコールスタックから、 'flags', 'loop' を削除してます。 'flags' は、なんやかんやの辞書 info 上に移動してます。 def do_global(lst, verb): genv = {} - inf = { 'name' : None, 'env' : genv, 'flags' : [], 'loop' : 0 } + inf = { 'name' : None, 'env' : genv } call_stk = [ inf ] - info = { 'lst' : lst, 'call_stk' : call_stk, 'genv' : genv, 'verb' : verb } + info = { 'lst' : lst, 'call_stk' : call_stk, 'genv' : genv, 'flags' : [], 'verb' : verb } blk = [ 'br_s', '{', lst ] do_expr(blk, info) info.get('call_stk').pop() グローバル変数を生成するために main 関数の前の実行時に積むこーるスタックから、 'flags', 'loop' を削除してます。 'flags' は、なんやかんやの辞書 info 上に移動してます。
それでは実行。
$ cp esh.py esi.py $ cat esi.py-diff.txt | patch とりあえず、最近試したソースで $ ./esi.py for2.c ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 $ $ ./esi.py type.c $ $ ./esi.py global.c ; echo $? 1 $ ./esi.py global.c g ; echo $? 123 $ そして $ ./esi.py sin2.c @ --- info --- call_stk: - env: a: 0.0 ac: 1 av: [sin2.c] i: 1.0 m: 10.0 n: 20.0 r: 10.0 name: main flags: [] genv: {M_PI: 3.141592653589793, sin: !!python/name:math.sin '', stdout_flush: !!python/name:None.flush '', stdout_write: !!python/name:None.write ''} lst: '...' verb: 0 Err alredy exist 'a' $ 変化なしで、悪化はしてないですね。
では次の段階へ。
@@ -356,9 +356,6 @@ def es_split(s): def get_call_inf(info): return info.get('call_stk')[-1] -def get_env(info, g=False): - return info.get('genv') if g else get_call_inf(info).get('env') - def get_flags(info): return info.get('flags') @@ -369,30 +366,34 @@ def was_flags(info, f): flags.remove(f) return ret -def is_exist(name, info, g=None): - if g is not None: - return name in get_env(info, g) - return is_exist(name, info, g=False) or is_exist(name, info, g=True) +def find_env(name, info, genv=None): + rstk = [ inf for inf in reversed( info.get('call_stk') ) ] + i = next( ( i for (i, inf) in enumerate(rstk) if inf.get('name') ), 0 ) + for inf in rstk[:i+1]: + env = inf.get('env') + if name in env: + return env + if genv is None: + genv = info.get('genv') + return genv if name in genv else None コールスタックのカレントの env と genv だけ対象にしてた get_env() と、それを使ってた is_exist() は廃止にします。 それらの機能を含む find_env(name, info, genv=None) を追加します。 ローカル変数用のコールスタックの辞書 (for 文などのブロック用のスタックを含む)、 グローバル変数用の辞書 (独自指定も可能) の順で、指定の名前を含むか判定し、 見つかれば、変数名を含む辞書 env を返します。 見つからなければ None を返します。 name : 変数用の辞書群から探しだす名前を指定します。 info : なんやかんやの辞書です。 genv : 指定しないで None の場合、グローバル変数の領域として、 通常のグローバル変数用の辞書である info 上の 'genv' を対象にします。 genv として辞書の指定があれば、その辞書をグローバル変数用の辞書として、 name を探します。 あえて空の辞書 {} を指定すると、グローバル変数領域では見つからないので、 ローカル変数領域だけ探す事になります。 def get_val(name, info): - if is_exist(name, info, g=False): - return get_env(info).get(name) - if is_exist(name, info, g=True): - return get_env(info, g=True).get(name) + env = find_env(name, info) + if env: + return env.get(name) err_not_found(name) def set_val(name, val, info): - if is_exist(name, info, g=False): - get_env(info)[name] = val - return val - if is_exist(name, info, g=True): - get_env(info, g=True)[name] = val + env = find_env(name, info) + if env: + env[name] = val return val err_not_found(name) find_env() を使うように書き換えてます。 def new_val(name, val, info): - if not is_exist(name, info, g=False): - get_env(info)[name] = val + env = get_call_inf(info).get('env') + if name not in env: + env[name] = val return val err_exit("alredy exist '{}'".format(name)) 廃止した is_exist() をつかわないように変更しています。 @@ -565,7 +566,7 @@ def do_expr(expr, info): e = expr[2][0] return do_expr(e, info) if v == '{': - do_blk(expr, info) + do_blk(None, {}, expr, info) return None if k == 'op': return do_op( v, expr[2:], info ) do_blk() の引数を追加しています。 @@ -585,16 +586,19 @@ def do_expr(expr, info): warn_no_sup('do_expr', '[k, v]', [k, v]) return None -def do_blk(blk, info): +def do_blk(name, env, blk, info): + call_stk = info.get('call_stk') + call_stk.append( { 'name' : name, 'env' : env } ) (_, _, lst) = blk # [ 'br_s', '{', lst ] call_inf = get_call_inf(info) for expr in lst: do_expr(expr, info) if 'ret' in info: - return + break flags = get_flags(info) if 'break' in flags or 'continue' in flags: - return + break + call_stk.pop() def get_sys_func(name): try: do_blk() の引数を追加しています。 先頭でスタックを積み 末尾でスタックを降ろしています。 @@ -612,7 +616,8 @@ def do_fcall(name, args, info): f = lambda (k, nm): k == 'fdef' and nm == name fdef = next( ( e for e in lst if f( e[:2] ) ), None ) if fdef is None: - func = get_val(name, info) if is_exist(name, info) else get_sys_func(name) + env = find_env(name, info) + func = env.get(name) if env else get_sys_func(name) if func: return func(*args) err_not_found("fdef '{}'".format(name)) 廃止した is_exist() から追加した find_env() に変更しています。 @@ -622,10 +627,7 @@ def do_fcall(name, args, info): names = [ e[1] for e in args_lst if e[0] == 'name' ] env = dict( zip(names, args) ) - call_stk = info.get('call_stk') - call_stk.append( { 'name' : name, 'env' : env } ) - do_blk(body, info) - call_inf = call_stk.pop() + do_blk(name, env, body, info) was_flags(info, 'break') was_flags(info, 'continue') do_blk() 内でスタックを操作するようにしたので、 呼び元側での処理を削除しています。 @@ -634,12 +636,9 @@ def do_fcall(name, args, info): def do_global(lst, verb): genv = {} - inf = { 'name' : None, 'env' : genv } - call_stk = [ inf ] - info = { 'lst' : lst, 'call_stk' : call_stk, 'genv' : genv, 'flags' : [], 'verb' : verb } + info = { 'lst' : lst, 'call_stk' : [], 'genv' : genv, 'flags' : [], 'verb' : verb } blk = [ 'br_s', '{', lst ] - do_expr(blk, info) - info.get('call_stk').pop() + do_blk(None, genv, blk, info) return info def warn_no_sup(f, t, v): do_blk() 内でスタックを操作するようにしたので、 呼び元側での処理を削除しています。
それでは実行。
$ cp esi.py esj.py $ cat esj.py-diff.txt | patch $ ./esj.py sin2.c @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ この表示が出るまで長い道のりでした。
既出の sin2.c の本質は後半部分にあります。
$ cat sin2.c #include <stdio.h> #include <math.h> /*-->| only interpreter void *stdout_write = eval("sys.stdout.write") void *stdout_flush = eval("sys.stdout.flush") sys_exec("global math ; import math") double M_PI = eval("math.pi"); void *sin = eval("math.sin"); |<--*/ // only interpreter //|--> only compiler void stdout_write(char *s) { printf("%s", s); } void stdout_flush(void) { fflush(stdout); } //<--| // only compiler void show_str(char *s) { stdout_write(s); stdout_flush(); } void show_spc(int n) { int i; for (i=0; i<n; i++) show_str(" "); } int main(int ac, char **av) { double r = 10.0; int i, n = 20; for (i=0; i<<n; i++) { double a = 2 * M_PI * i / n; int m = sin(a) * r + r; show_spc(m); show_str("@\n"); } return 0; } $
前半の stdout_flush() 定義までは、他でも使い回せる道具なので、 ライブラリ的に別のソースに分けて使えるようにしてみましょう。
といってもインクルードとかは面倒なので、 例によって卑怯な方法で試してみます。
@@ -666,6 +666,13 @@ def err_exit(msg, rcode=1): print('Err {}'.format(msg)) sys.exit(rcode) +def load_src(fn): + if fn == '-': + return sys.stdin.read() + with open(fn, 'r') as f: + s = f.read() + return s + if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: @@ -677,8 +684,7 @@ if __name__ == "__main__": if opt in argv: argv.remove(opt) verb = i + 1 - with open(argv[1], 'r') as f: - s = f.read() + s = load_src( argv[1] ) lst = es_split(s) if verb >= 3: show_yaml(lst, 'lst') .c のソースファイルの指定として '-' を許すようにしています。 その場合は標準入力からソースコードをロードします。
これで cat が使えるはずですね。
$ cp esj.py esk.py $ cat esk.py-diff.txt | patch $ cat lib.c #include <stdio.h> #include <math.h> /*-->| only interpreter void *stdout_write = eval("sys.stdout.write") void *stdout_flush = eval("sys.stdout.flush") sys_exec("global math ; import math") double M_PI = eval("math.pi"); void *sin = eval("math.sin"); |<--*/ // only interpreter //|--> only compiler void stdout_write(char *s) { printf("%s", s); } void stdout_flush(void) { fflush(stdout); } //<--| // only compiler $ $ cat sin3.c #include <math.h> void stdout_write(char *s); void stdout_flush(void); void show_str(char *s) { stdout_write(s); stdout_flush(); } void show_spc(int n) { int i; for (i=0; i<n; i++) show_str(" "); } int main(int ac, char **av) { double r = 10.0; int i, n = 20; for (i=0; i<n; i++) { double a = 2 * M_PI * i / n; int m = sin(a) * r + r; show_spc(m); show_str("@\n"); } return 0; } $ と分離しておいて コンパイラでは $ gcc -o sin3 sin3.c lib.c -lm $ ./sin3 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ インタプリタでは $ cat sin3.c lib.c | ./esk.py - @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ sin3.c 冒頭の include 行は このインタプリタにとっては '#' から行末までのコメントになります。 直後のプロトタイプ宣言も do_expr() 関数の処理は、 do nothing の箇所で、何もしません。 コンパイラのために追加しているこれらの箇所は、 インタプリタには何も影響ありません。 めでたしめでたし。
ぼちぼちと、それらしく実行できるようになってきたました。
ここらで、先送りしていた演算子の処理を出来る範囲で追加しておきます。
sin 関数繋がりで、エスケープシーケンスを使ったデモなど作ってみます。
まだまだ機能が足りて無かったり、バグの地雷が潜んでるはずなので、 慎重に地雷を避けながら作らねばなりません。
慎重に地雷を避けながらコーディングしてみました。
まずコンパイラで試してみます。 $ gcc -o sin4 sin4.c lib2.c -lm $ ./sin4デフォルトは10秒で終了します。 コマンド引数 -sec xxx で変更できます。 画面のサイズのデフォルトは80x25で、 -w xxx -h yyy で変更できます。 $ ./sin4 -w 150 -h 50 -sec 60 などと指定します。
で、インタプリタに演算子追加だけでいけるかと思いきや、 やっぱりバグを踏んでました。
@@ -292,7 +292,7 @@ def es_split(s): [ 2, '>', [ '&&' ] ], [ 2, '>', [ '||' ] ], [ 3, '<', [ '?', ':' ] ], - [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '~=', '>>=', '<<=' ] ], + [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=' ] ], [ 2, '>', [ ',' ] ], # ... ] @@ -416,6 +416,10 @@ def do_op1(v, a, info): a = do_expr(a, info) if v == '-': return -a + if v == '!': + return not a + if v == '~': + return ~a if v == '++': return do_set(oa, a+1, info) if v == '--': @@ -441,6 +445,30 @@ def do_op2(v, a, b, info): if v == '-=': a -= b return do_set(oa, a, info) + if v == '*=': + a *= b + return do_set(oa, a, info) + if v == '/=': + a /= b + return do_set(oa, a, info) + if v == '%=': + a %= b + return do_set(oa, a, info) + if v == '&=': + a &= b + return do_set(oa, a, info) + if v == '|=': + a |= b + return do_set(oa, a, info) + if v == '^=': + a ^= b + return do_set(oa, a, info) + if v == '>>=': + a >>= b + return do_set(oa, a, info) + if v == '<<=': + a <<= b + return do_set(oa, a, info) if v == '+': return a + b @@ -450,6 +478,12 @@ def do_op2(v, a, b, info): return a * b if v == '/': return a / b + if v == '%': + return a % b + if v == '<<': + return a << b + if v == '>>': + return a >> b if v == '==': return a == b if v == '!=': @@ -462,6 +496,16 @@ def do_op2(v, a, b, info): return a >= b if v == '>': return a > b + if v == '&': + return a & b + if v == '^': + return a ^ b + if v == '|': + return a | b + if v == '&&': + return a and b + if v == '||': + return a or b if v == ',': return b 演算子の処理を追加しています。 @@ -501,6 +545,8 @@ def do_kwd(v, expr, info): if v == 'while': while do_expr( expr[2], info ): do_expr( expr[3], info ) + if 'ret' in info: + break if was_flags(info, 'break'): break was_flags(info, 'continue') @@ -514,6 +560,8 @@ def do_kwd(v, expr, info): do_expr( args[0], info ) while do_expr( args[1], info ): do_expr( expr[3], info ) + if 'ret' in info: + break if was_flags(info, 'break'): break was_flags(info, 'continue') ここが踏んでた地雷の修正です。 ループで return しても抜けれてませんでした。 このバグは lib2.c int opt_idx(char *key, int ac, char **av) { int i; for (i=0; i<ac; i++) if(strcmp(av[i], key) == 0) return i; return -1; } ここでコマンド引数の判定でループが抜けれなくて、 パラメータの変更が出来ないでいました。
それでは実行。
$ cp esk.py esl.py $ cat esl.py-diff.txt | patch $ cat sin4.c lib2.c | ./esl.py -$ cat sin4.c lib2.c | ./esl.py - -w 40 -h 20 -sec 60
爆弾処理したカイあって、まともに動くようになりました。
sin4.c で避けた地雷の一つはここでした。
int main(int ac, char **av) { int n = 5, m = 3, i = 0, j, run_sec = 10; double hz = 100, hz_x[m], hz_y[m]; : hz_x[i] = 0.23, hz_y[i] = 0.49, i++; hz_x[i] = 0.33, hz_y[i] = 0.19, i++; hz_x[i] = 0.41, hz_y[i] = 0.31, i++; :
ここは配列の初期化を使いたいところですね。対応を入れてみます。
@@ -582,17 +582,30 @@ def do_comma(e, info): lst = flat_comma(e) return [ do_expr(e, info) for e in lst ] +def do_type_arr(e, val, info): + idx = e[2] + n = do_expr(idx, info) + if n is not None: + n = int(n) + if val and val[:2] == [ 'br_s', '{' ]: + val = val[2][0] + val = do_comma(val, info); + if n and n > len(val): + val += [ None ] * ( n - len(val) ) + else: + val = [ None ] * n + return val + do_type() の処理で配列の場合の処理関数を追加しています。 初期値代入の右辺を val で受け取って、 可能ならば評価結果のリストにして返します。 def do_type(expr, info): for e in flat_comma( expr[2] ): val = None if e[:2] == [ 'op', '=' ]: - val = do_expr( e[3], info ) + val = e[3] e = e[2] if e[0] == 'arr': - idx = e[2] - n = int( do_expr(idx, info) ) - if val is None: - val = [ None ] * n + val = do_type_arr(e, val, info) + elif val: + val = do_expr(val, info) name = e[1] new_val(name, val, info) return None do_type() で初期値代入の右辺の評価を少し後ろに遅らせて、 配列の時の処理を do_type_arr() にのれん分けしてます。 @@ -611,8 +624,8 @@ def do_expr(expr, info): return get_val(v, info) if k == 'br_s': if v in [ '(', '[' ]: # '[' for do_arr() - e = expr[2][0] - return do_expr(e, info) + lst = expr[2] + return do_expr( lst[0], info ) if len(lst) > 0 else None if v == '{': do_blk(None, {}, expr, info) return None do_expr() で配列の添字などカッコの中の評価では、 カッコの中が空の場合 None を返すように処理を追加しています。
それでは実行。
$ cp esl.py esm.py $ cat esm.py-diff.txt | patch $ diff -up -L before -L after sin4.c sin5.c --- before +++ after @@ -22,15 +22,13 @@ void step(double hz_x, double hz_y, doub int main(int ac, char **av) { - int n = 5, m = 3, i = 0, j, run_sec = 10; - double hz = 100, hz_x[m], hz_y[m]; + int n = 5, m = 3, i, j, run_sec = 10; + double hz = 100; + double hz_x[] = {0.23, 0.33, 0.41}; + double hz_y[] = {0.49, 0.19, 0.31}; double t = 1.0 / hz, ela, nt = 1; double sta = sys_sec(), sec, prev = 0; - hz_x[i] = 0.23, hz_y[i] = 0.49, i++; - hz_x[i] = 0.33, hz_y[i] = 0.19, i++; - hz_x[i] = 0.41, hz_y[i] = 0.31, i++; - w = opt_int("-w", ac, av, w); h = opt_int("-h", ac, av, h); run_sec = opt_int("-sec", ac, av, run_sec); $ $ cat sin5.c lib2.c | ./esm.py -
配列の初期化クリアです。 (目標達成の意味のクリアです。BSS クリアとかじゃなくて)
lib2.c で避けた地雷の一つはここでした。
if (a == b) return 0; if (a < b) return -1; return 1;
C言語ならば、三項演算子で
return (a == b) ? 0 : (a < b) ? -1 : 1;
にしたいですよね。 (本来カッコは不要ですが、見易くするために入れてます)
さてどう実装すべしか?
...まてよ。 e1 ? e2 : e3
の三項ですが...
'?' という二項演算子と ':' という二項演算子として処理しても何とかなるのでは?
ops を見てみると
ops = [ # term, bind, lst : [ 2, '>', [ '||' ] ], [ 3, '<', [ '?', ':' ] ], [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=' ] ], :
ここの 3 を 2 に変更したとして、結合規則も '<--' こっち向きです。
e1 ? e2 : e3 を tree_op() で組み上げると [ op, ?, e1, [ op, :, e2, e3 ] ] にしてくれるだろうし e1 ? e2 : e31 ? e32 : e33 だと [ op, ?, e1, [ op, :, e2, [ op, ? e31, [ op, :, e32, e33 ] ] ] ] にしてくれるはずでは?
ならば term を 2 として扱って、 do_op2() に処理を追加するだけで実現できそうな気がします。
その前に、まず簡単なプログラムで、簡単に正解の動作を確かめておきましょう。
$ cat san.c int main(int ac, char **av) { int a = ac % 2 ? ac : ac == 2 ? 100 + ac : 200 + ac; return a; } $ コマンド引数の個数 (./san を含む) が、 奇数のときは、そのまま表示 2 のときは、100 を足して表示 それ以外の偶数のときは、200を足して表示してみます。 $ gcc -o san san.c $ $ ./san ; echo $? 1 $ ./san a ; echo $? 102 $ ./san a b ; echo $? 3 $ ./san a b c ; echo $? 204 $ ./san a b c d ; echo $? 5 $ 意図した通りの動作です。
では、三項演算子追加の改造です。
@@ -291,7 +291,7 @@ def es_split(s): [ 2, '>', [ '|' ] ], [ 2, '>', [ '&&' ] ], [ 2, '>', [ '||' ] ], - [ 3, '<', [ '?', ':' ] ], + [ 2, '<', [ '?', ':' ] ], # term 3 :-p) [ 2, '<', [ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=' ] ], [ 2, '>', [ ',' ] ], # ... ] @@ -436,6 +436,8 @@ def do_op1(v, a, info): def do_op2(v, a, b, info): oa = a a = do_expr(a, info) + if v == '?': + return do_expr(b[2] if a else b[3], info) b = do_expr(b, info) if v == '=': return do_set(oa, b, info) たったこれだけなので、何だか拍子抜けですね。
それでは実行。
$ cp esm.py esn.py $ cat esn.py-diff.txt | patch $ ./esn.py -vvv san.c --- lst --- - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [int] - - op - '=' - [name, a] - - op - '?' - - op - '%' - [name, ac] - [num, '2'] - - op - ':' - [name, ac] - - op - '?' - - op - == - [name, ac] - [num, '2'] - - op - ':' - - op - + - [num, '100'] - [name, ac] - - op - + - [num, '200'] - [name, ac] - [etc, ;] : 思惑通り op の tree が組み上がってる様子です。 $ ./esn.py san.c ; echo $? 1 $ ./esn.py san.c a ; echo $? 102 $ ./esn.py san.c a b ; echo $? 3 $ ./esn.py san.c a b c; echo $? 204 $ ./esn.py san.c a b c d; echo $? 5 $ 動作も大丈夫そうですね。
それではこちらも。
$ diff -up -L before -L after lib2.c lib3.c --- before +++ after @@ -24,9 +24,7 @@ void *NULL = 0; int strcmp(char *a, char *b) { - if (a == b) return 0; - if (a < b) return -1; - return 1; + return (a == b) ? 0 : (a < b) ? -1 : 1; } |<--*/ // only interpreter $ $ cat sin5.c lib3.c | ./esn.py - -w 30 -h 15
lib3.c の strcmp() は opt_idx() から使っていて、 コマンド引数指定の "-w" や "-h" を判定できているようですね。
構造体も避けた地雷の一つです。
難易度の敷居が高そうですが、まずは「なんちゃって」でさらっといってみます。
ターゲットとして簡単な構造体の使用例で様子を見てみましょう。
$ cat struct.c struct foo { int a, b; char *s; }; struct bar { int x, y; char *s; } bar; int main(int ac, char **av) { struct foo foo; struct { int n1, n2; } hoge; foo.a = 1; foo.b = 2; foo.s = "foo"; bar.x = 10; bar.y = 20; hoge.n1 = 0; hoge.n2 = 100; return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; } $ まずコンパイラでの正解はこちら $ gcc -o struct struct.c $ ./struct ; echo $? 133 $
様子見のなんちゃって構造体対応です。
@@ -231,6 +231,12 @@ def tree_kwd(lst, kdic): r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] return [ e ] + r + if v == 'struct': + (name, i) = ( lst[1][1], 2 ) if lst[1][0] == 'name' else (None, 1) + (body, i) = ( lst[i], i+1 ) if lst[i][:2] == [ 'br_s', '{' ] else (None, i) + e = [ 'struct', name, body ] + e = [ 'type' if lst[i][0] == 'name' else 'sdef', e ] + return step(e, i) e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) 関数 tree_kwd() に [ 'kwd', 'struct' ] をまとめる処理を追加してます。 エラーチェックはハショってます。 'struct'を見つけると [ 'struct' name, body ] (name や body は None の場合もあります) の形式にしてから、さらに直後に [ name, xxx ] が続くようなら 構造体の変数宣言と見なして [ 'type', [ 'struct', name, body ] ] の形式に、そうでなければ構造体定義と見なして [ 'sdef', [ 'struct', name, body ] ] の形式にしています。 @@ -308,7 +314,7 @@ def es_split(s): 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, 'type' : [ 'int', 'char', 'void', 'double' ], - 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue' ], + 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue', 'struct' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';' ], 'no_spc': [ '.0', '.1', '.2', '.3', '.4', '.5', '.6', '.7', '.8', '.9' ], kdic の 'kwd' に 'struct' の登録を追加しています。 @@ -408,6 +414,12 @@ def do_set(e, val, info): arr = get_arr_with_chk(v, i, info) arr[i] = val return val + if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: + name = e[2][1] # [ 'name', xxx ] + memb = e[3][1] # [ 'name', xxx ] + d = get_val(name, info) + d[memb] = val + return val warn_no_sup('do_set', 'k', k) 変数への代入処理の関数 do_set() で、 右辺に演算子 '.' や '->' がきたときの処理を追加しています。 @@ -438,6 +450,8 @@ def do_op2(v, a, b, info): a = do_expr(a, info) if v == '?': return do_expr(b[2] if a else b[3], info) + if v in [ '.', '->' ]: + return a.get( b[1] ) # b == [ name, xxx ] b = do_expr(b, info) if v == '=': return do_set(oa, b, info) 二項演算子の処理関数 do_op2() で、 演算子 '.' や '->' の処理を追加しています。 @@ -608,6 +622,8 @@ def do_type(expr, info): val = do_type_arr(e, val, info) elif val: val = do_expr(val, info) + elif expr[1][0] == 'struct': + val = {} name = e[1] new_val(name, val, info) return None 変数宣言の処理関数 do_type() で、 型に 'struct' が指定されている場合の処理を追加しています。 値を空の辞書にしてるだけで、構造体の初期化は未対応です。 @@ -643,7 +659,7 @@ def do_expr(expr, info): if k == 'type': return do_type(expr, info) - if k in [ 'fproto', 'etc', 'fdef' ]: # do nothing !!! + if k in [ 'fproto', 'etc', 'fdef', 'sdef' ]: # do nothing !!! return None warn_no_sup('do_expr', '[k, v]', [k, v]) 無視して何も処理しないリストに、構造体の定義 'sdef' の登録を追加しています。
それでは実行。
$ cp esn.py eso.py $ cat eso.py-diff.txt | patch $ ./eso.py struct.c ; echo $? 133 $ 大丈夫そうです。 $ ./eso.py -vvv struct.c | fold --- lst --- - - sdef - - struct - foo - - br_s - '{' - - - type - [int] - - op - ',' - [name, a] - [name, b] - [etc, ;] - - type - - [char, '*'] - [name, s] - [etc, ;] - [etc, ;] - - type - - struct - bar - - br_s - '{' - - - type - [int] - - op - ',' - [name, x] - [name, y] - [etc, ;] - - type - - [char, '*'] - [name, s] - [etc, ;] - [name, bar] - [etc, ;] - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - [struct, foo, null] - [name, foo] - [etc, ;] - - type - - struct - null - - br_s - '{' - - - type - [int] - - op - ',' - [name, n1] - [name, n2] - [etc, ;] - [name, hoge] - [etc, ;] - - op - '=' - - op - . - [name, foo] - [name, a] - [num, '1'] - [etc, ;] - - op - '=' - - op - . - [name, foo] - [name, b] - [num, '2'] - [etc, ;] - - op - '=' - - op - . - [name, foo] - [name, s] - [str, '"', foo] - [etc, ;] - - op - '=' - - op - . - [name, bar] - [name, x] - [num, '10'] - [etc, ;] - - op - '=' - - op - . - [name, bar] - [name, y] - [num, '20'] - [etc, ;] - - op - '=' - - op - . - [name, hoge] - [name, n1] - [num, '0'] - [etc, ;] - - op - '=' - - op - . - [name, hoge] - [name, n2] - [num, '100'] - [etc, ;] - - kwd - return - - op - + - - op - + - - op - + - - op - + - - op - + - - op - . - [name, foo] - [name, a] - - op - . - [name, foo] - [name, b] - - op - . - [name, bar] - [name, x] - - op - . - [name, bar] - [name, y] - - op - . - [name, hoge] - [name, n1] - - op - . - [name, hoge] - [name, n2] - [etc, ;] expr=['sdef', ['struct', 'foo', ['br_s', '{', [['type', ['int']], ['op', ',', [' name', 'a'], ['name', 'b']], ['etc', ';'], ['type', [['char', '*']]], ['name', ' s'], ['etc', ';']]]]] : do_fcall() name=main args=[1, ['struct.c']] expr=['type', ['struct', 'foo', None], ['name', 'foo']] expr=['etc', ';'] expr=['type', ['struct', None, ['br_s', '{', [['type', ['int']], ['op', ',', ['n ame', 'n1'], ['name', 'n2']], ['etc', ';']]]], ['name', 'hoge']] expr=['etc', ';'] :
とりあえずターゲットの struct.c としてはクリアです。
ですが構造体の課題、まだまだありそうです。
なんちゃってとは言え、構造体の初期化くらいは使えたいですよね。
いろいろ不具合もあろうかと思いますが、とりあえず版です。
@@ -201,7 +201,7 @@ def tree_kwd(lst, kdic): return lst e = lst[0] step = lambda e, i: [ e ] + tree_kwd( lst[i:], kdic ) - top_is = lambda lst, k, v: len(lst) > 0 and lst[0][:2] == [ k, v ] + top_is = lambda lst, t: len(lst) > 0 and lst[0][:len(t)] == t if type(e) is not list: return step(e, 1) (k, v) = e[:2] コード整理も兼ねてます。 top_is() の仕様を変えてます。 lst の先頭の要素が存在して、かつその要素がリストで、 かつその先頭部分がリスト t に合致したら True です。 @@ -216,26 +216,27 @@ def tree_kwd(lst, kdic): r = tree_kwd( lst[1:], kdic ) return [ [ k, v, r[0] ] ] + r[1:] if v == 'if' and len( lst[2:] ) > 0: - if lst[1][:2] != [ 'br_s', '(' ]: + if not top_is( lst[1:], [ 'br_s', '(' ] ): err_not_found("'(' after 'if'") r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] - if top_is(r, 'etc', ';'): + if top_is(r, [ 'etc', ';' ] ): r.pop(0) - if top_is(r, 'kwd', 'else'): + if top_is(r, [ 'kwd', 'else' ] ): e += [ r.pop(0)[2] ] return [ e ] + r if v in [ 'while', 'for' ] and len( lst[2:] ) > 0: - if lst[1][:2] != [ 'br_s', '(' ]: + if not top_is( lst[1:], [ 'br_s', '(' ] ): err_not_found("'(' after '{}'".format(v)) r = tree_kwd( lst[2:], kdic ) e = [ k, v, lst[1], r.pop(0) ] return [ e ] + r if v == 'struct': - (name, i) = ( lst[1][1], 2 ) if lst[1][0] == 'name' else (None, 1) - (body, i) = ( lst[i], i+1 ) if lst[i][:2] == [ 'br_s', '{' ] else (None, i) + (name, i) = ( lst[1][1], 2 ) if top_is( lst[1:], [ 'name' ] ) else (None, 1) + (body, i) = ( lst[i], i+1 ) if top_is( lst[i:], [ 'br_s', '{' ] ) else (None, i) e = [ 'struct', name, body ] - e = [ 'type' if lst[i][0] == 'name' else 'sdef', e ] + grp = 'sdef' if top_is( lst[i:], [ 'etc', ';' ] ) else 'type' + e = [ grp, e ] return step(e, i) e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) なるべく仕様変更した top_is() を使うように更新しています。 末尾の箇所も top_is() を使うようにしています。 @@ -246,18 +247,22 @@ def tree_type(lst): e = lst[0] step = lambda e, i: [ e ] + tree_type( lst[i:] ) if e[0] == 'br_s': - e = [ e[0], e[1], tree_type( e[2] ) ] + e = e[:2] + [ tree_type( e[2] ) ] return step(e, 1) + + tree_type_br_s = lambda br_s: tree_type( [ br_s ] )[0] if e[0] == 'fdef': - e4 = tree_type( [ e[4] ] )[0] - e = [ e[0], e[1], e[2], e[3], e4 ] + e = e[:4] + [ tree_type_br_s( e[4] ) ] return step(e, 1) if e[0] == 'kwd' and e[1] in [ 'if', 'while', 'for' ]: - try_blk = lambda t: tree_type( [ t ] )[0] if t[:2] == [ 'br_s', '{' ] else t + try_blk = lambda t: tree_type_br_s(t) if t[:2] == [ 'br_s', '{' ] else t e = e[:2] + [ try_blk(t) for t in e[2:] ] return step(e, 1) + if ( e[0] == 'type' or e[0] == 'sdef' ) and e[1][0] == 'struct' and e[1][2] is not None: + f = lambda s: s[:2] + [ tree_type_br_s( s[2] ) ] + e = e[:1] + [ f( e[1] ) ] if e[0] == 'type' and len( lst[1:] ) > 0: - e = [ e[0], e[1], lst[1] ] + e = e[:2] + [ lst[1] ] return step(e, 2) return step(e, 1) tree_type() の中も少々整理してます。 + if ( e[0] == 'type' or e[0] == 'sdef' ) and e[1][0] == 'struct' and e[1][2] is not None: + f = lambda s: s[:2] + [ tree_type_br_s( s[2] ) ] + e = e[:1] + [ f( e[1] ) ] この箇所が新規追加部分です。 [ 'type', [ 'struct', name, body ] ] あるいは [ 'sdef', [ 'struct', name, body ] ] を見つけると body の中も再帰で処理するように追加してます。 @@ -396,10 +401,17 @@ def set_val(name, val, info): return val err_not_found(name) +def add_order(name, env): + k = '@order' + if k not in env: + env[k] = [] + env.get(k).append(name) + def new_val(name, val, info): env = get_call_inf(info).get('env') if name not in env: env[name] = val + add_order(name, env) return val err_exit("alredy exist '{}'".format(name)) new_val() で変数生成して辞書に登録を追加していますが、 辞書なので生成順の情報は記録されません。 単純な構造体初期化のための初期値の並びは、 メンバ変数の登場順に依存しますよね。(たぶん) そのためメンバ変数を生成した順番を記録しておきたく、 関数 add_order() を追加してます。 @@ -612,6 +624,23 @@ def do_type_arr(e, val, info): val = [ None ] * n return val +def sdef_name(name): + return 'sdef@' + name + +def do_type_struct(e, val, info): + # [ 'struct', name, body ] + if val is None: + return {} + (name, body) = e[1:3] + if body is None: + body = get_val( sdef_name(name), info ) + benv = {} + do_blk(None, benv, body, info) + names = benv.get('@order') + vals = do_comma(val[2][0], info) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + vals += [ None ] * ( len(names) - len(vals) ) + return dict( zip(names, vals) ) + def do_type(expr, info): for e in flat_comma( expr[2] ): val = None do_type() の中の 'struct' 関連の処理を do_type_struct() として、のれん分けです。 ここが、今回のキモです。 構造体の変数宣言で初期値が指定されていた場合、 sdef として記録されている構造体定義情報から、 body をひっぱってきます。 そして構造体定義の body からメンバ変数名の並びを取得します。 その取得処理が + benv = {} + do_blk(None, benv, body, info) + names = benv.get('@order') ここです。 空の benv 辞書を用意して do_blk() を呼び出して、構造体定義の { ... } 部分を、 単なるブロックとして「実行」させます。 「実行」しても、メンバ変数としての変数宣言しか無いはずなので、 benv 上に 「変数」が生成されるだけで戻ってきます。 そして、先の new_val() で追加した順番のしくみを使って、 '@order' というリストを取り出すと、 生成した順にメンバ変数の名前が並んでいるはずです。 つづいて初期値の側 + vals = do_comma(val[2][0], info) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + vals += [ None ] * ( len(names) - len(vals) ) + return dict( zip(names, vals) ) 値の方の { xxx, xxx, ... } は、演算子のまとめ処理をくぐってるので、 [ br_s, {, [ [ op, ',' ... ] ] ] 形式の ',' のツリーになってるはずです。 do_comma() でフラットに展開して評価した結果を vals に。 メンバの数より初期値の数が少ない場合は None で埋めて、 「メンバ名 : 値」形式の辞書を生成して、構造体の初期値として返します。 C言語には None などありませんが「なんちゃって」という事で... @@ -620,14 +649,20 @@ def do_type(expr, info): e = e[2] if e[0] == 'arr': val = do_type_arr(e, val, info) + elif expr[1][0] == 'struct': + val = do_type_struct( expr[1], val, info ) elif val: val = do_expr(val, info) - elif expr[1][0] == 'struct': - val = {} name = e[1] new_val(name, val, info) return None do_type() での 'struct' 関連の処理を少々前に移動してます。 これまでは構造体の初期化は未対応ということで、 初期値がある場合は、構造体では無い前提としていました。 +def do_sdef(expr, info): + # [ 'sdef', [ 'struct', name, body ] ] + (name, body) = expr[1][1:3] + if name and body: + new_val( sdef_name(name), body, info ) + def do_expr(expr, info): if info.get('verb') >= 2: print('expr={}'.format(expr)) @@ -658,8 +693,10 @@ def do_expr(expr, info): return do_fcall(v, args, info) if k == 'type': return do_type(expr, info) + if k == 'sdef': + return do_sdef(expr, info) - if k in [ 'fproto', 'etc', 'fdef', 'sdef' ]: # do nothing !!! + if k in [ 'fproto', 'etc', 'fdef' ]: # do nothing !!! return None warn_no_sup('do_expr', '[k, v]', [k, v]) これまで構造体定義 'sdef' は無視してきましたが、 初期化時に body の情報が必要なので、 関数 do_sdef() を追加して処理するようにしています。 struct xxx の名前 xxx と 定義本体の body を new_val() で辞書に登録してます。 その際、通常の変数名と空間を分けるため、 sdef_name(name) で名前を変換して、 「sdef@xxx」という特別な変数名で、値 body を登録しています。
それでは実行。
$ cat struct2.c struct foo { int a, b; char *s; }; struct bar { int x, y; char *s; } bar = { 10, 20, "bar" }; int main(int ac, char **av) { struct foo foo = { 1, 2, "foo" }; struct { int n1, n2; } hoge = { 0, 100 }; return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; } $ $ diff -u -L before -L after struct.c struct2.c --- before +++ after @@ -6,25 +6,15 @@ struct bar { int x, y; char *s; -} bar; +} bar = { 10, 20, "bar" }; int main(int ac, char **av) { - struct foo foo; + struct foo foo = { 1, 2, "foo" }; struct { int n1, n2; - } hoge; - - foo.a = 1; - foo.b = 2; - foo.s = "foo"; - - bar.x = 10; - bar.y = 20; - - hoge.n1 = 0; - hoge.n2 = 100; + } hoge = { 0, 100 }; return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; } $ 変数宣言箇所で初期値を与えて初期化するように変更しています。 $ cp eso.py esp.py $ cat esp.py-diff.txt | patch $ ./esp.py struct2.c ; echo $? 133 $ コンパイラも確認 $ gcc -o struct2 struct2.c $ ./struct2 ; echo $? 133 $
「なんちゃって」版の構造体の初期化クリアです。
構造体の入れ子にも対応してみましょう。
ここまでは構造体のメンバが別の構造体だなんて、夢にも思っておりませんでした。
まずターゲットのプログラムをば。
$ cat struct3.c #include "lib4.h" struct foo { int a, b; char *s; }; struct bar { int x, y; struct foo f; char *s; } bar = { 10, 20, { 3, 4, "FOO" }, "bar" }; int main(int ac, char **av) { struct foo foo = { 1, 2, "foo" }; struct { int n1, n2; struct bar b; } hoge = { 0, 100, { 11, 21, { 5, 6, "fOo" }, "BAR" } }; show_str("foo.a="); show_int(foo.a); show_nl(); show_str("foo.b="); show_int(foo.b); show_nl(); show_str("foo.s="); show_str(foo.s); show_nl(); show_str("bar.x="); show_int(bar.x); show_nl(); show_str("bar.y="); show_int(bar.y); show_nl(); show_str("bar.f.a="); show_int(bar.f.a); show_nl(); show_str("bar.f.b="); show_int(bar.f.b); show_nl(); show_str("bar.f.s="); show_str(bar.f.s); show_nl(); show_str("bar.s="); show_str(bar.s); show_nl(); show_str("hoge.n1="); show_int(hoge.n1); show_nl(); show_str("hoge.n2="); show_int(hoge.n2); show_nl(); show_str("hoge.b.x="); show_int(hoge.b.x); show_nl(); show_str("hoge.b.y="); show_int(hoge.b.y); show_nl(); show_str("hoge.b.f.a="); show_int(hoge.b.f.a); show_nl(); show_str("hoge.b.f.b="); show_int(hoge.b.f.b); show_nl(); show_str("hoge.b.f.s="); show_str(hoge.b.f.s); show_nl(); show_str("hoge.b.s="); show_str(hoge.b.s); show_nl(); return 0; } $ $ diff -up -L before -L after struct2.c struct3.c --- before +++ after @@ -1,3 +1,5 @@ +#include "lib4.h" + struct foo { int a, b; char *s; @@ -5,8 +7,9 @@ struct foo { struct bar { int x, y; + struct foo f; char *s; -} bar = { 10, 20, "bar" }; +} bar = { 10, 20, { 3, 4, "FOO" }, "bar" }; int main(int ac, char **av) { @@ -14,7 +17,29 @@ int main(int ac, char **av) struct { int n1, n2; - } hoge = { 0, 100 }; + struct bar b; + } hoge = { 0, 100, { 11, 21, { 5, 6, "fOo" }, "BAR" } }; + + show_str("foo.a="); show_int(foo.a); show_nl(); + show_str("foo.b="); show_int(foo.b); show_nl(); + show_str("foo.s="); show_str(foo.s); show_nl(); + + show_str("bar.x="); show_int(bar.x); show_nl(); + show_str("bar.y="); show_int(bar.y); show_nl(); + show_str("bar.f.a="); show_int(bar.f.a); show_nl(); + show_str("bar.f.b="); show_int(bar.f.b); show_nl(); + show_str("bar.f.s="); show_str(bar.f.s); show_nl(); + show_str("bar.s="); show_str(bar.s); show_nl(); + + show_str("hoge.n1="); show_int(hoge.n1); show_nl(); + show_str("hoge.n2="); show_int(hoge.n2); show_nl(); + + show_str("hoge.b.x="); show_int(hoge.b.x); show_nl(); + show_str("hoge.b.y="); show_int(hoge.b.y); show_nl(); + show_str("hoge.b.f.a="); show_int(hoge.b.f.a); show_nl(); + show_str("hoge.b.f.b="); show_int(hoge.b.f.b); show_nl(); + show_str("hoge.b.f.s="); show_str(hoge.b.f.s); show_nl(); + show_str("hoge.b.s="); show_str(hoge.b.s); show_nl(); - return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; + return 0; } $ bar の中に foo を入れ子にして、 hoge の中に bar を入れ子にしてみました。 そして lib 側も lib3 から lib4 へバージョンアップ。 ヘッダファイルの番号も 4 に合わせました。 $ diff -up -L before -L after lib2.h lib4.h --- before +++ after @@ -1,5 +1,5 @@ -#ifndef __LIB2_H__ -#define __LIB2_H__ +#ifndef __LIB4_H__ +#define __LIB4_H__ #include <sys/time.h> #include <stdio.h> @@ -11,8 +11,10 @@ void stdout_write(char *s); void stdout_flush(void); void show_str(char *s); +void show_nl(void); void show_spc(int n); void show_v(int v, int by_asc); +void show_f(double v); void show_int(int v); void show_esc(void); void cls(void); $ $ diff -up -L before -L after lib3.c lib4.c --- before +++ after @@ -1,4 +1,4 @@ -#include "lib2.h" +#include "lib4.h" /*-->| only interpreter @@ -49,9 +49,17 @@ void show_str(char *s) stdout_flush(); } +void show_nl(void) +{ + show_str("\n"); +} + void show_spc(int n) { int i; + /*-->| only interpreter + n = sys_int(n); + |<--*/ // only interpreter for (i=0; i<n; i++) show_str(" "); } @@ -62,11 +70,19 @@ void show_v(int v, int by_asc) //<--| // only compiler /*-->| only interpreter - char *s; int i = sys_int(v); - if (by_asc) s = str(i); - else s = chr(i); - stdout_write(s); + stdout_write(by_asc ? str(i) : chr(i)); + |<--*/ // only interpreter +} + +void show_f(double v) +{ + //|--> only compiler + printf("%f", v); + //<--| // only compiler + + /*-->| only interpreter + stdout_write(str(v)); |<--*/ // only interpreter } $ コンパイラによる正解は $ gcc -o struct3 struct3.c lib4.c -lm $ ./struct3 foo.a=1 foo.b=2 foo.s=foo bar.x=10 bar.y=20 bar.f.a=3 bar.f.b=4 bar.f.s=FOO bar.s=bar hoge.n1=0 hoge.n2=100 hoge.b.x=11 hoge.b.y=21 hoge.b.f.a=5 hoge.b.f.b=6 hoge.b.f.s=fOo hoge.b.s=BAR $
@@ -234,6 +234,8 @@ def tree_kwd(lst, kdic): if v == 'struct': (name, i) = ( lst[1][1], 2 ) if top_is( lst[1:], [ 'name' ] ) else (None, 1) (body, i) = ( lst[i], i+1 ) if top_is( lst[i:], [ 'br_s', '{' ] ) else (None, i) + if body is not None: + body = body[:2] + [ tree_kwd( body[2], kdic ) ] e = [ 'struct', name, body ] grp = 'sdef' if top_is( lst[i:], [ 'etc', ';' ] ) else 'type' e = [ grp, e ] キーワードをまとめる処理の 'struct' の箇所です。 構造体定義の body の中にも入れ子の 'struct' があり得るので、 再帰呼び出しを追加しています。 @@ -627,6 +629,19 @@ def do_type_arr(e, val, info): def sdef_name(name): return 'sdef@' + name +def bind_body_val(lst, vals): + if len(lst) == 0 or len(vals) == 0: + return (lst, vals) + e = lst[0] + if e[0] == 'type' or e[:2] == [ 'op', ',' ]: + (r, vals) = bind_body_val( e[2:], vals ) + e = e[:2] + r + elif e[0] == 'name': + e = [ 'op', '=', e, vals[0] ] + vals = vals[1:] + (r, vals) = bind_body_val( lst[1:], vals ) + return ( [ e ] + r, vals) + def do_type_struct(e, val, info): # [ 'struct', name, body ] if val is None: @@ -634,12 +649,14 @@ def do_type_struct(e, val, info): (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) + + vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + (r, vals) = bind_body_val( body[2], vals ) + body = body[:2] + [ r ] + benv = {} do_blk(None, benv, body, info) - names = benv.get('@order') - vals = do_comma(val[2][0], info) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] - vals += [ None ] * ( len(names) - len(vals) ) - return dict( zip(names, vals) ) + return benv def do_type(expr, info): for e in flat_comma( expr[2] ): 前回のキモだった箇所の関数 do_type_struct() です。 今回もここが最も重要です。 これまでは do_blk() 呼び出しで、benv 上に値 None の変数を生成してから、 後で初期値 vals と合成して、新たな辞書を生成していました。 今回は do_blk() 呼び出し前に、body の方に初期値を追加する戦略です。 追加してる関数 bind_body_val() に、body 本体のリストと初期値のリストを渡し、 '=' 演算子を追加しつつ初期値を割り振ってまわります。 そして結果のリストと余った初期値を返します。 初期値の余りは再帰呼び出しのためのものなので、 最初の呼び元では通常、空のリストが返るはずです。 @@ -650,6 +667,7 @@ def do_type(expr, info): if e[0] == 'arr': val = do_type_arr(e, val, info) elif expr[1][0] == 'struct': + do_sdef( expr[1], info ) val = do_type_struct( expr[1], val, info ) elif val: val = do_expr(val, info) ここは前回からの抜けです。 構造体の変数宣言で構造体名と body があって定義にもなってる場合、 body 情報を 'sdef@xxx' で登録する処理が抜けてました。 @@ -657,9 +675,9 @@ def do_type(expr, info): new_val(name, val, info) return None -def do_sdef(expr, info): - # [ 'sdef', [ 'struct', name, body ] ] - (name, body) = expr[1][1:3] +def do_sdef(e, info): + # [ 'struct', name, body ] + (name, body) = e[1:3] if name and body: new_val( sdef_name(name), body, info ) @@ -694,7 +712,7 @@ def do_expr(expr, info): if k == 'type': return do_type(expr, info) if k == 'sdef': - return do_sdef(expr, info) + return do_sdef(expr[1], info) if k in [ 'fproto', 'etc', 'fdef' ]: # do nothing !!! return None do_sdef() の仕様を少し変更してます。 前回からの抜け対応で 'sdef' 以外に 'type' からも使うようになったので、 'struct' 以降の形式を渡すように変更しています。
それでは実行。
$ cp esp.py esq.py $ cat esq.py-diff.txt | patch $ cat struct3.c lib4.c | ./esq.py - foo.a=1 foo.b=2 foo.s=foo bar.x=10 bar.y=20 bar.f.a=3 bar.f.b=4 bar.f.s=FOO bar.s=bar hoge.n1=0 hoge.n2=100 hoge.b.x=11 hoge.b.y=21 hoge.b.f.a=5 hoge.b.f.b=6 hoge.b.f.s=fOo hoge.b.s=BAR $ $ ./struct3 > struct3.txt $ cat struct3.c lib4.c | ./esq.py - | diff -u struct3.txt - $ 一致。
構造体の入れ子 struct3.c の範囲はクリアです。
メンバ変数に配列を持つ構造体。
構造体のメンバ変数の生成も、ローカル変数などの通常の変数の生成も、 同じ do_blk() を使っています。
ローカル変数で配列を扱えているならば、原理的にメンバ変数でも大丈夫なハズ。 確かめてみましょう。
$ cat arr_memb.c #include "lib4.h" int main(int ac, char **av) { struct fuga { int a, b[3]; } c = { 1, { 2, 3, 4 } }; show_str("c.a="); show_int(c.a); show_nl(); show_str("c.b[0]="); show_int(c.b[0]); show_nl(); show_str("c.b[1]="); show_int(c.b[1]); show_nl(); show_str("c.b[2]="); show_int(c.b[2]); show_nl(); return 0; } $ 例によってコンパイラによる正解は $ gcc -o arr_memb arr_memb.c lib4.c -lm $ ./arr_memb c.a=1 c.b[0]=2 c.b[1]=3 c.b[2]=4 $ んだば $ cat arr_memb.c lib4.c | ./esq.py - c.a=1 c.b[0]=Traceback (most recent call last): File "./esq.py", line 831, inrcode = do_fcall( 'main', args, info ) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 713, in do_expr return do_type(expr, info) File "./esq.py", line 673, in do_type val = do_expr(val, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 756, in do_fcall return func(*args) TypeError: int() argument must be a string or a number, not 'list' うおおっとっと。 c.a=1 c.b[0]= までは出てるので、c.b[0] のアクセス。 ああ、もしかして。 $ cat arr_memb.c lib4.c | ./esq.py -vvv - : - - fcall - show_str - - br_s - ( - - [str, '"', 'c.b[0]='] - [etc, ;] - - fcall - show_int - - br_s - ( - - - op - . - [name, c] - - arr - b - - br_s - '[' - - [num, '0'] - [etc, ;] : c.b[0] が [name, c] '.' [arr, b, [ br_s, [, [num, 0] ] ] ] なので c . ( b[0] ) と解釈されてます。 man operator の表示 Operator Associativity -------- ------------- () [] -> . left to right なので本来は、同じ優先順位なら (c . b) [0] を期待してるのですが... def es_split(s): : lst = tree_bra(lst, kdic) lst = name_bra(lst) lst = tree_op(lst, ops) : name_bra() で「名前 + カッコ類」のまとめ上げを先にしてから、 tree_op() で '.' を含む演算子をまとめてました。 man operator によると配列アクセスの'[...]'も本当は演算子なんですね。 例えば x + y[1+1] c.b[2+3] なら x + y '[' (1+1) c . b '[' (2+3] のように'[...]'を丸カッコに置き換えて、 '[' という二項演算子のように扱えば良さげです。 優先順位であえてカッコをつけると x + ( y '[' (1+1) ) ( c . b ) '[' (2+3)
大手術の予感ですが、配列アクセスのカッコを演算子として扱うように変更してみます。
@@ -103,20 +103,20 @@ def name_bra(lst): tbl = [ [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ], [ 'br_s', '{' ] ], lambda type, name, (k, v, args), (k2, v2, body): - [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] ], + [ [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] ] ], [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ] ], - lambda type, name, (k, v, args): [ 'fproto', name[1], [ k, v, name_bra(args) ], type ] ], + lambda type, name, (k, v, args): [ [ 'fproto', name[1], [ k, v, name_bra(args) ], type ] ] ], [ [ [ 'name' ], [ 'br_s', '(' ] ], - lambda name, (k, v, args): [ 'fcall', name[1], [ k, v, name_bra(args) ] ] ], + lambda name, (k, v, args): [ [ 'fcall', name[1], [ k, v, name_bra(args) ] ] ] ], [ [ [ 'name' ], [ 'br_s', '[' ] ], - lambda name, (k, v, idx): [ 'arr', name[1], [ k, v, name_bra(idx) ] ] ], + lambda name, (k, v, idx): [ name, [ 'op', '[' ], [ k, '(', name_bra(idx) ] ] ], [ [ [ 'br_s' ] ], - lambda (k, v, slst): [ k, v, name_bra(slst) ] ] + lambda (k, v, slst): [ [ k, v, name_bra(slst) ] ] ] ] for (pat, f) in tbl: if is_match(lst, pat): pn = len(pat) - return [ f( *lst[:pn] ) ] + name_bra(lst[pn:]) + return f( *lst[:pn] ) + name_bra( lst[pn:] ) return [ lst[0] ] + name_bra(lst[1:]) tstk = [] 関数 name_bra() は「名前 + カッコ」のパターンを見つけて 'fdef', 'fprototype', 'fcall', 'arr' などにまとめる処理をしていました。 'arr' は演算子 '[' として処理するので、 [ 'op', '[', ... ] の形式にまとめるように変更しています。 その際、この時点までの '[' としての [ 'br_s', '[', ... ] は、 '(' に変更して xxx[ yyy ] は xxx '[' ( yyy ) になるように、まとめています。 for 文の本体では f() が [ k, v ... ] 形式を返す事を期待していましたが、 今回追加の配列の箇所では [ [ k, v ... ], [ k, v ... ], ... ] のリストを返したい...と。 全てのデータ側でリスト形式に変更して、for 文側でのリスト化を外しました。 (見づらく分かりにくくなってしまった) @@ -177,7 +177,7 @@ def tree_op(lst, ops): if k == 'br_s': e = [ k, v, tree_op( e[2], ops ) ] - elif k in [ 'fcall', 'arr' ]: + elif k == 'fcall': e = [ k, v, tree_op( [ e[2] ], ops )[0] ] elif k == 'fdef': e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] 'arr' は演算子に任せる事にしたので削除しています。 @@ -185,7 +185,7 @@ def tree_op(lst, ops): if k == 'op': return tstk_pop_ret( tree_op1(lst, ops, k, v) ) - if k in [ 'num', 'name', 'arr', 'fcall', 'br_s', 'str' ] and len( lst[1:] ) > 0: + if k in [ 'num', 'name', 'fcall', 'br_s', 'str' ] and len( lst[1:] ) > 0: e1 = lst[1] (k1, v1) = e1[:2] if k1 == 'op': 'arr' は演算子に任せる事にしたので削除しています。 @@ -358,6 +358,8 @@ def es_split(s): lst = name_bra(lst) + ops[0][2].append('[') # !!! + lst = tree_op(lst, ops) lst = tree_kwd(lst, kdic) es_split() での name_bra() 呼び出しまでは、 '[' は br_s グループのカッコとして扱います。 これで従来通り、カッコの中の複雑な処理も従来のままです。 ここ以降は演算子のテーブル ops の最も優先順位の高いリストに登録を追加して、 tree_op() 以降は演算子として扱います。 @@ -422,12 +424,6 @@ def do_set(e, val, info): if k == 'name': set_val(v, val, info) return val - if k == 'arr': - idx = e[2] - i = int( do_expr(idx, info) ) - arr = get_arr_with_chk(v, i, info) - arr[i] = val - return val if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: name = e[2][1] # [ 'name', xxx ] memb = e[3][1] # [ 'name', xxx ] 'arr' は演算子に任せる事にしたので削除しています。 @@ -467,6 +463,8 @@ def do_op2(v, a, b, info): if v in [ '.', '->' ]: return a.get( b[1] ) # b == [ name, xxx ] b = do_expr(b, info) + if v == '[': + return a[ int(b) ] if v == '=': return do_set(oa, b, info) if v == '+=': 二項演算子の処理関数 do_op2() に、 演算子 '[' の処理を追加しています。 b の評価結果は float なので int に変換しています。 @@ -550,17 +548,6 @@ def do_op(v, args, info): warn_no_sup('do_op', 'term', term) -def get_arr_with_chk(name, i, info): - arr = get_val(name, info) - if i >= len(arr): - err_exit('over acc {}[{}]'.format(name, i)) - return arr - -def do_arr(name, idx, info): - i = int( do_expr(idx, info) ) - arr = get_arr_with_chk(name, i, info) - return arr[i] - def do_kwd(v, expr, info): if v == 'return': r = do_expr( expr[2], info ) 'arr' は演算子に任せる事にしたので削除しています。 @@ -612,20 +599,6 @@ def do_comma(e, info): lst = flat_comma(e) return [ do_expr(e, info) for e in lst ] -def do_type_arr(e, val, info): - idx = e[2] - n = do_expr(idx, info) - if n is not None: - n = int(n) - if val and val[:2] == [ 'br_s', '{' ]: - val = val[2][0] - val = do_comma(val, info); - if n and n > len(val): - val += [ None ] * ( n - len(val) ) - else: - val = [ None ] * n - return val - def sdef_name(name): return 'sdef@' + name 'arr' は演算子に任せる事にしたので削除しています。 @@ -636,7 +609,7 @@ def bind_body_val(lst, vals): if e[0] == 'type' or e[:2] == [ 'op', ',' ]: (r, vals) = bind_body_val( e[2:], vals ) e = e[:2] + r - elif e[0] == 'name': + elif e[0] == 'name' or e[:2] == [ 'op' ,'[' ]: e = [ 'op', '=', e, vals[0] ] vals = vals[1:] (r, vals) = bind_body_val( lst[1:], vals ) 構造体の初期化で body のメンバ変数宣言の中に、 '= 初期値' を追加してまわる処理の箇所です。 メンバ変数が配列の形式の場合、 この箇所が '[' 演算子にまとめられてここに来るはずです。 その対応の追加です。 (ここを見逃していてデバッグでハマりました) @@ -644,34 +617,54 @@ def bind_body_val(lst, vals): def do_type_struct(e, val, info): # [ 'struct', name, body ] - if val is None: - return {} (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) - - vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] - (r, vals) = bind_body_val( body[2], vals ) - body = body[:2] + [ r ] - + if val is not None: + vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + (r, vals) = bind_body_val( body[2], vals ) + body = body[:2] + [ r ] benv = {} do_blk(None, benv, body, info) return benv ここはバグ修正です。 構造体の初期値がない場合、無条件で空の辞書を初期値にしていました。 それぞれのメンバ変数名に値 None を設定した辞書になるよう、変更しています。 +def do_type_val(typ, arr_n, val, info): + if arr_n is False: + if typ[0] != 'struct': + if val is None: + return None + return do_expr(val, info) + return do_type_struct(typ, val, info) + + arr_n = do_expr(arr_n, info) + if arr_n is not None: + arr_n = int(arr_n) + vals = [] + if val is not None: + vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + if arr_n is None: + arr_n = len(vals) + else: + vals_n = len(vals) + if vals_n < arr_n: + vals += [ None ] * (arr_n - vals_n) + elif arr_n < vals_n: + vals = vals[:arr_n] + return [ do_type_val(typ, False, val, info) for val in vals ] + def do_type(expr, info): + typ = expr[1] for e in flat_comma( expr[2] ): + arr_n = False val = None if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] - if e[0] == 'arr': - val = do_type_arr(e, val, info) - elif expr[1][0] == 'struct': - do_sdef( expr[1], info ) - val = do_type_struct( expr[1], val, info ) - elif val: - val = do_expr(val, info) + if e[:2] == [ 'op', '[' ]: + arr_n = e[3] + e = e[2] name = e[1] + val = do_type_val(typ, arr_n, val, info) new_val(name, val, info) return None do_type() で変数の初期値を用意する処理を、 do_type_val() にのれん分けしています。 arr_n は [ ... ] の中の箇所です。 配列の宣言で要素数の指定が無ければ None、 そもそも配列でなければ False です。 @@ -694,7 +687,7 @@ def do_expr(expr, info): if k == 'name': return get_val(v, info) if k == 'br_s': - if v in [ '(', '[' ]: # '[' for do_arr() + if v == '(': lst = expr[2] return do_expr( lst[0], info ) if len(lst) > 0 else None if v == '{': 'arr' は演算子に任せる事にしたので削除しています。 @@ -702,8 +695,6 @@ def do_expr(expr, info): return None if k == 'op': return do_op( v, expr[2:], info ) - if k == 'arr': - return do_arr( v, expr[2], info ) if k == 'fcall': (_, _, args) = expr[2] if len(args) > 0: 'arr' は演算子に任せる事にしたので削除しています。
それでは実行。
$ cp esq.py esr.py $ cat esr.py-diff.txt | patch $ cat arr_memb.c lib4.c | ./esr.py - c.a=1 c.b[0]=2 c.b[1]=3 c.b[2]=4 $
配列のメンバ変数クリアです。
続いて構造体の配列。
ターゲットは次の通りです。
$ cat struct_arr.c #include "lib4.h" int main(int ac, char **av) { struct guha { int a, b; } c[3] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; int i; for (i=0; i<3; i++) { show_str("c["); show_int(i); show_str("].a="); show_int( c[i].a ); show_nl(); show_str("c["); show_int(i); show_str("].b="); show_int( c[i].b ); show_nl(); } return 0; } $ コンパイラの正解は $ gcc -o struct_arr struct_arr.c lib4.c -lm $ ./struct_arr c[0].a=1 c[0].b=2 c[1].a=3 c[1].b=4 c[2].a=5 c[2].b=6 $
で、実はこれ、もう大丈夫かも。
配列のメンバ変数 の大手術が成功していれば、 「構造体の配列」も問題なくなってるのでは?
$ cat struct_arr.c lib4.c | ./esr.py - c[0].a=1 c[0].b=2 c[1].a=3 c[1].b=4 c[2].a=5 c[2].b=6 $
このターゲットは「変更なし」でクリアです。
ではエスケープシーケンスなデモで避けていた「地雷」の除去確認を。
$ diff -up -L before -L after sin5.c sin6.c --- before +++ after @@ -1,4 +1,4 @@ -#include "lib2.h" +#include "lib4.h" int w = 80; int h = 25; @@ -24,8 +24,7 @@ int main(int ac, char **av) { int n = 5, m = 3, i, j, run_sec = 10; double hz = 100; - double hz_x[] = {0.23, 0.33, 0.41}; - double hz_y[] = {0.49, 0.19, 0.31}; + struct { double x, y; } hzs[] = { { 0.23, 0.49 }, { 0.33, 0.19 }, { 0.41, 0.31 } }; double t = 1.0 / hz, ela, nt = 1; double sta = sys_sec(), sec, prev = 0; @@ -38,7 +37,7 @@ int main(int ac, char **av) for (i=0; i<m; i++){ for (j=0; j<n; j++){ double add = nt * j / n; - step(hz_x[i], hz_y[i], sec + add, prev + add); + step(hzs[i].x, hzs[i].y, sec + add, prev + add); } } ela = sec - prev; $ コンパイラの正解は $ gcc -o sin6 sin6.c lib4.c -lm $ ./sin6 いつもの表示ですね。 そしてインタプリタ $ cat sin6.c lib4.c | ./esr.py - いつもの表示で大丈夫です。
ここまで変数や構造体のメンバ変数について、型は「ほぼ」無視してきました。
構造体かそうでないかの区別程度です。
まぁ地雷を避けてそれで何とかなる範囲しか相手にしないので、それはそれで良いかと。
にしても数値は全部 float で扱っているので、変に気をつける必要があります。 例えばこんなターゲット。
$ cat int.c #include "lib4.h" int foo(int v) { show_f(v) ; show_nl(); return v; } int bar(void) { double d = 1.5; return d; } int main(int ac, char **av) { double d = 1.5; int i, n = d; for (i=0; i<n; i++); show_int(i); show_nl(); show_f( foo(d) ); show_nl(); show_f( bar() ); show_nl(); return 0; } $ コンパイラで実行すると $ gcc -o int int.c lib4.c -lm $ ./int 1 1.000000 1.000000 1.000000 $ インタプリタでは $ cat int.c lib4.c | ./esr.py - 2 1.5 1.5 1.5 $ なんとも残念な結果に。
int型の変数への代入と、関数呼び出しでint型への引数への代入、そして関数の返り値がint型の場合です。
面倒なので、この場面でint型の変換だけは面倒を見ることにします。
@@ -628,14 +628,7 @@ def do_type_struct(e, val, info): do_blk(None, benv, body, info) return benv -def do_type_val(typ, arr_n, val, info): - if arr_n is False: - if typ[0] != 'struct': - if val is None: - return None - return do_expr(val, info) - return do_type_struct(typ, val, info) - +def do_type_arr(typ, arr_n, val, info): arr_n = do_expr(arr_n, info) if arr_n is not None: arr_n = int(arr_n) do_type_val() の後半の配列用の処理を do_type_arr() として、のれん分けしています。 do_type_val() 本体の前半は次へ。 @@ -652,6 +645,21 @@ def do_type_val(typ, arr_n, val, info): vals = vals[:arr_n] return [ do_type_val(typ, False, val, info) for val in vals ] +def try_int(typ, v): + return int(v) if typ == [ 'int' ] and v is not None else v + +def do_type_val(typ, arr_n, val, info): + if arr_n is not False: + return do_type_arr(typ, arr_n, val, info) + + if typ[0] == 'struct': + return do_type_struct(typ, val, info) + + if val is None: + return None + + return try_int( typ, do_expr(val, info) ) + def do_type(expr, info): typ = expr[1] for e in flat_comma( expr[2] ): do_type_val() を整理しています。 try_int() を追加して、 配列でも構造体でも無い場合に、 最後に値を返す直前で整数なら変換をかけています。 @@ -749,7 +757,9 @@ def do_fcall(name, args, info): (args_info, ret_type, body) = fdef[2:] (_, _, args_lst) = args_info + types = [ e[1] for e in args_lst if e[0] == 'type' ] names = [ e[1] for e in args_lst if e[0] == 'name' ] + args = [ try_int(t, v) for (t, v) in zip(types, args) ] env = dict( zip(names, args) ) do_blk(name, env, body, info) do_fcall() で関数呼出しの際、 引数をスタックに積むところで、 先程追加した try_int() を使うようにしています。 @@ -757,7 +767,7 @@ def do_fcall(name, args, info): was_flags(info, 'break') was_flags(info, 'continue') ret = info.pop('ret', None) - return ret + return try_int( ret_type[1], ret ) def do_global(lst, verb): genv = {} 同じく do_fcall() で、 関数の返り値を返す直前にも try_int() 呼出しを追加しています。
それでは実行。
$ cp esr.py ess.py $ cat ess.py-diff.txt | patch $ cat int.c lib4.c | ./ess.py - 1 1 1 1 $ コンパイラの結果 $ ./int 1 1.000000 1.000000 1.000000 $ 表示形式が違いますが、まぁよしとして...。
これで以前に密かに気になっていた事も解消されるはずです。
ライブラリ的に別のソースに分けてみる のところで試した sin3.c 。
コンパイラでは $ gcc -o sin3 sin3.c lib.c -lm $ ./sin3 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ インタプリタでは $ cat sin3.c lib.c | ./esk.py - @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ 微妙に違ってて、実はこれ sin3.c の void show_spc(int n) { int i; for (i=0; i<n; i++) show_str(" "); } に由来していました。 関数の引数の型は int ですが、おかまいなしに float の値が渡されてきていました。 そして、これを嫌って最新の lib4.c でも密かに void show_spc(int n) { int i; /*-->| only interpreter n = sys_int(n); |<--*/ // only interpreter for (i=0; i<n; i++) show_str(" "); } などと対策してありました。 さぁもう大丈夫なはずです。 $ cat sin3.c lib.c | ./ess.py - @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ $ ./sin3 > sin3.txt cat sin3.txt @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $ $ cat sin3.c lib.c | ./ess.py - | diff -u sin3.txt - $ 一致です。
せっかくなので lib の方も余計な対策を外しておきます。
$ diff -up -L before -L after lib4.c lib5.c --- before +++ after @@ -1,4 +1,4 @@ -#include "lib4.h" +#include "lib5.h" /*-->| only interpreter @@ -57,9 +57,6 @@ void show_nl(void) void show_spc(int n) { int i; - /*-->| only interpreter - n = sys_int(n); - |<--*/ // only interpreter for (i=0; i<n; i++) show_str(" "); } @@ -70,8 +67,7 @@ void show_v(int v, int by_asc) //<--| // only compiler /*-->| only interpreter - int i = sys_int(v); - stdout_write(by_asc ? str(i) : chr(i)); + stdout_write(by_asc ? str(v) : chr(v)); |<--*/ // only interpreter } $ ヘッダの方は名前の変更だけです。 $ diff -up -L before -L after lib4.h lib5.h --- before +++ after @@ -1,5 +1,5 @@ -#ifndef __LIB4_H__ -#define __LIB4_H__ +#ifndef __LIB5_H__ +#define __LIB5_H__ #include <sys/time.h> #include <stdio.h> $
ちょこちょこ変更してきたので「デグレ」を確認して対策しておきます。
まず配列関係から $ ls -1 *arr*.c arr.c arr_memb.c struct_arr.c $ $ cat arr.c int main(int ac, char **av) { int a[] = { 1, 2 }; return a[0] + a[1]; } $ $ ./ess.py arr.c ; echo $? 3 $ $ cat arr_memb.c #include "lib4.h" int main(int ac, char **av) { struct fuga { int a, b[3]; } c = { 1, { 2, 3, 4 } }; show_str("c.a="); show_int(c.a); show_nl(); show_str("c.b[0]="); show_int(c.b[0]); show_nl(); show_str("c.b[1]="); show_int(c.b[1]); show_nl(); show_str("c.b[2]="); show_int(c.b[2]); show_nl(); return 0; } $ $ cat arr_memb.c lib4.c | ./ess.py - c.a=1 c.b[0]=2 c.b[1]=3 c.b[2]=4 $ $ cat struct_arr.c #include "lib4.h" int main(int ac, char **av) { struct guha { int a, b; } c[3] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; int i; for (i=0; i<3; i++) { show_str("c["); show_int(i); show_str("].a="); show_int( c[i].a ); show_nl(); show_str("c["); show_int(i); show_str("].b="); show_int( c[i].b ); show_nl(); } return 0; } $ $ cat struct_arr.c lib4.c | ./ess.py - c[0].a=1 c[0].b=2 c[1].a=3 c[1].b=4 c[2].a=5 c[2].b=6 $ つぎ構造体 $ ls -1 *struct*.c struct.c struct2.c struct3.c struct_arr.c $ struct_arr.c は配列のところで確認したので省略します。 $ cat struct.c struct foo { int a, b; char *s; }; struct bar { int x, y; char *s; } bar; int main(int ac, char **av) { struct foo foo; struct { int n1, n2; } hoge; foo.a = 1; foo.b = 2; foo.s = "foo"; bar.x = 10; bar.y = 20; hoge.n1 = 0; hoge.n2 = 100; return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; } $ $ cat struct.c | ./ess.py - ; echo $? 133 $ $ cat struct2.c struct foo { int a, b; char *s; }; struct bar { int x, y; char *s; } bar = { 10, 20, "bar" }; int main(int ac, char **av) { struct foo foo = { 1, 2, "foo" }; struct { int n1, n2; } hoge = { 0, 100 }; return foo.a + foo.b + bar.x + bar.y + hoge.n1 +hoge.n2; } $ $ cat struct2.c | ./ess.py - ; echo $? 133 $ $ cat struct3.c #include "lib4.h" struct foo { int a, b; char *s; }; struct bar { int x, y; struct foo f; char *s; } bar = { 10, 20, { 3, 4, "FOO" }, "bar" }; int main(int ac, char **av) { struct foo foo = { 1, 2, "foo" }; struct { int n1, n2; struct bar b; } hoge = { 0, 100, { 11, 21, { 5, 6, "fOo" }, "BAR" } }; show_str("foo.a="); show_int(foo.a); show_nl(); show_str("foo.b="); show_int(foo.b); show_nl(); show_str("foo.s="); show_str(foo.s); show_nl(); show_str("bar.x="); show_int(bar.x); show_nl(); show_str("bar.y="); show_int(bar.y); show_nl(); show_str("bar.f.a="); show_int(bar.f.a); show_nl(); show_str("bar.f.b="); show_int(bar.f.b); show_nl(); show_str("bar.f.s="); show_str(bar.f.s); show_nl(); show_str("bar.s="); show_str(bar.s); show_nl(); show_str("hoge.n1="); show_int(hoge.n1); show_nl(); show_str("hoge.n2="); show_int(hoge.n2); show_nl(); show_str("hoge.b.x="); show_int(hoge.b.x); show_nl(); show_str("hoge.b.y="); show_int(hoge.b.y); show_nl(); show_str("hoge.b.f.a="); show_int(hoge.b.f.a); show_nl(); show_str("hoge.b.f.b="); show_int(hoge.b.f.b); show_nl(); show_str("hoge.b.f.s="); show_str(hoge.b.f.s); show_nl(); show_str("hoge.b.s="); show_str(hoge.b.s); show_nl(); return 0; } $ $ cat struct3.c lib4.c | ./ess.py - : genv: '@order': [sdef@foo, bar, stdout_write, stdout_flush, sys_int, M_PI, sin, sys_sec, sys_sleep, 'NULL'] M_PI: 3.141592653589793 'NULL': 0.0 bar: '@order': [x, y, f, s] f: '@order': [a, b, s] a: 3 b: 4 s: FOO s: bar x: 10 y: 20 sdef@foo: - br_s - '{' - - - type - [int] - - op - ',' - [name, a] - [name, b] - [etc, ;] - - type - - [char, '*'] - [name, s] - [etc, ;] sin: !!python/name:math.sin '' stdout_flush: !!python/name:None.flush '' stdout_write: !!python/name:None.write '' sys_int: !!python/name:__builtin__.int '' sys_sec: !!python/name:time.time '' sys_sleep: !!python/name:time.sleep '' lst: '...' verb: 0 Err not found sdef@bar $ でました。 構造体 bar の body が辞書に登録されてない? 遡って試してみると大手術前の $ cat struct3.c lib4.c | ./esq.py - foo.a=1 foo.b=2 foo.s=foo bar.x=10 bar.y=20 bar.f.a=3 bar.f.b=4 bar.f.s=FOO bar.s=bar hoge.n1=0 hoge.n2=100 hoge.b.x=11 hoge.b.y=21 hoge.b.f.a=5 hoge.b.f.b=6 hoge.b.f.s=fOo hoge.b.s=BAR $ ここまでは正常です。 $ cat esr.py-diff.txt : +def do_type_val(typ, arr_n, val, info): + if arr_n is False: + if typ[0] != 'struct': + if val is None: + return None + return do_expr(val, info) + return do_type_struct(typ, val, info) + + arr_n = do_expr(arr_n, info) + if arr_n is not None: + arr_n = int(arr_n) + vals = [] + if val is not None: + vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] + if arr_n is None: + arr_n = len(vals) + else: + vals_n = len(vals) + if vals_n < arr_n: + vals += [ None ] * (arr_n - vals_n) + elif arr_n < vals_n: + vals = vals[:arr_n] + return [ do_type_val(typ, False, val, info) for val in vals ] + def do_type(expr, info): + typ = expr[1] for e in flat_comma( expr[2] ): + arr_n = False val = None if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] - if e[0] == 'arr': - val = do_type_arr(e, val, info) - elif expr[1][0] == 'struct': - do_sdef( expr[1], info ) - val = do_type_struct( expr[1], val, info ) - elif val: - val = do_expr(val, info) + if e[:2] == [ 'op', '[' ]: + arr_n = e[3] + e = e[2] name = e[1] + val = do_type_val(typ, arr_n, val, info) new_val(name, val, info) return None : ここが原因でした。 do_type() からの do_type_struct() 呼び出しを削除して、 do_type_val() から do_type_struct() を呼びだすように移動してます。 ここで旧 do_type_struct() 呼び出しの直前にあった do_sdef() が抜け落ちてしまってました。 最新版 ess.py では def do_type_val(typ, arr_n, val, info): if arr_n is not False: return do_type_arr(typ, arr_n, val, info) if typ[0] == 'struct': return do_type_struct(typ, val, info) if val is None: return None return try_int( typ, do_expr(val, info) ) にまとめてるので def do_type_struct(e, val, info): # [ 'struct', name, body ] (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) if val is not None: vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] (r, vals) = bind_body_val( body[2], vals ) body = body[:2] + [ r ] benv = {} do_blk(None, benv, body, info) return benv こちら側の冒頭に do_sdef() を復活させておきます。
で、で、原因を追いかけてソースを眺めてて気付いたのですが
def do_set(e, val, info): (k, v) = e[:2] if k == 'name': set_val(v, val, info) return val if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: name = e[2][1] # [ 'name', xxx ] memb = e[3][1] # [ 'name', xxx ] d = get_val(name, info) d[memb] = val return val warn_no_sup('do_set', 'k', k) ここって配列への代入処理が抜けてる? ささっと試してみましょう。 arr2.c $ cat arr2.c int main(int ac, char **av) { int a[2]; a[0] = 1; a[1] = 2; return a[0] + a[1]; } $ $ diff -up -L before -L after arr.c arr2.c --- before +++ after @@ -1,5 +1,7 @@ int main(int ac, char **av) { - int a[] = { 1, 2 }; + int a[2]; + a[0] = 1; + a[1] = 2; return a[0] + a[1]; } $ $ gcc -o arr2 arr2.c $ ./arr2 ; echo $? 3 $ $ ./ess.py arr2.c ; echo $? Warn do_set() not support k='op' Warn do_set() not support k='op' Traceback (most recent call last): File "./ess.py", line 832, inrcode = do_fcall( 'main', args, info ) File "./ess.py", line 765, in do_fcall do_blk(name, env, body, info) File "./ess.py", line 728, in do_blk do_expr(expr, info) File "./ess.py", line 690, in do_expr return do_kwd(v, expr, info) File "./ess.py", line 553, in do_kwd r = do_expr( expr[2], info ) File "./ess.py", line 705, in do_expr return do_op( v, expr[2:], info ) File "./ess.py", line 547, in do_op return do_op2(v, args[0], args[1], info) File "./ess.py", line 502, in do_op2 return a + b TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType' 1 $ だー、やっぱり。 $ ./esq.py arr2.c ; echo $? 3 $ ./esr.py arr2.c ; echo $? Warn do_set() not support k='op' Warn do_set() not support k='op' Traceback (most recent call last): File "./esr.py", line 822, in rcode = do_fcall( 'main', args, info ) File "./esr.py", line 755, in do_fcall do_blk(name, env, body, info) File "./esr.py", line 720, in do_blk do_expr(expr, info) File "./esr.py", line 682, in do_expr return do_kwd(v, expr, info) File "./esr.py", line 553, in do_kwd r = do_expr( expr[2], info ) File "./esr.py", line 697, in do_expr return do_op( v, expr[2:], info ) File "./esr.py", line 547, in do_op return do_op2(v, args[0], args[1], info) File "./esr.py", line 502, in do_op2 return a + b TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType' 1 $ これまた大手術前まではいけてました。 けっこう手術失敗気味だったんですね。 $ cat esr.py-diff.txt : @@ -422,12 +424,6 @@ def do_set(e, val, info): if k == 'name': set_val(v, val, info) return val - if k == 'arr': - idx = e[2] - i = int( do_expr(idx, info) ) - arr = get_arr_with_chk(v, i, info) - arr[i] = val - return val if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: name = e[2][1] # [ 'name', xxx ] memb = e[3][1] # [ 'name', xxx ] : 確かに。do_set() の配列の処理を削除するだけで、 '[' 演算子の場合の処理に入れ替わってなかったです。
対処しておきます。
@@ -425,11 +425,15 @@ def do_set(e, val, info): set_val(v, val, info) return val if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: - name = e[2][1] # [ 'name', xxx ] + d = do_expr( e[2], info ) memb = e[3][1] # [ 'name', xxx ] - d = get_val(name, info) d[memb] = val return val + if e[:2] == [ 'op', '[' ]: + lst = do_expr( e[2], info ) + i = do_expr( e[3], info ) + lst[ int(i) ] = val + return val warn_no_sup('do_set', 'k', k) @@ -617,6 +621,7 @@ def bind_body_val(lst, vals): def do_type_struct(e, val, info): # [ 'struct', name, body ] + do_sdef(e, info) (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) ついでに、構造体メンバへの代入処理の箇所も修正です。
うーむ。ささっとデグレしてないかテストしたいところですね。 スクリプトを組んでみますかね。
$ cat test.sh #!/bin/bash ES=$1 chk() { NM=$1 LIB=$2 echo $NM if [ "$LIB" = "" ]; then gcc -o $NM $NM.c ./$NM ; echo $? > $NM.txt else gcc -o $NM $NM.c $LIB.c ./$NM > $NM.txt fi rm $NM if [ "$LIB" = "" ]; then cat $NM.c | ./$ES - ; echo $? > $NM.es.txt else cat $NM.c $LIB.c | ./$ES - > $NM.es.txt fi #echo hoge >> $NM.es.txt diff -u $NM.txt $NM.es.txt || { echo NG ; exit 1; } rm $NM.txt $NM.es.txt } chk arr chk arr2 chk struct chk struct2 chk struct3 lib4 chk arr_memb lib4 chk struct_arr lib4 # EOF $ $ ./test.sh est.py arr arr2 struct struct2 struct3 arr_memb struct_arr --- struct_arr.txt 2016-09-28 05:12:02.000000000 +0900 +++ struct_arr.es.txt 2016-09-28 05:12:02.000000000 +0900 @@ -1,6 +1,32 @@ -c[0].a=1 -c[0].b=2 -c[1].a=3 -c[1].b=4 -c[2].a=5 -c[2].b=6 +--- info --- +call_stk: +- env: + '@order': [sdef@guha] + ac: 1 + av: ['-'] + sdef@guha: + - br_s + - '{' + - - - type + - [int] + - - op + - ',' + - [name, a] + - [name, b] + - [etc, ;] + name: main +flags: [] +genv: + '@order': [stdout_write, stdout_flush, sys_int, M_PI, sin, sys_sec, sys_sleep, 'NULL'] + M_PI: 3.141592653589793 + 'NULL': 0.0 + sin: !!python/name:math.sin '' + stdout_flush: !!python/name:None.flush '' + stdout_write: !!python/name:None.write '' + sys_int: !!python/name:__builtin__.int '' + sys_sec: !!python/name:time.time '' + sys_sleep: !!python/name:time.sleep '' +lst: '...' +verb: 0 + +Err alredy exist 'sdef@guha' NG $ おっと struct_arr.c で NG でした。 ちなみに大手術前の esq.py の場合では $ ./test.sh esq.py arr arr2 struct struct2 struct3 arr_memb Traceback (most recent call last): File "./esq.py", line 831, inrcode = do_fcall( 'main', args, info ) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 764, in do_fcall do_blk(name, env, body, info) File "./esq.py", line 729, in do_blk do_expr(expr, info) File "./esq.py", line 713, in do_expr return do_type(expr, info) File "./esq.py", line 673, in do_type val = do_expr(val, info) File "./esq.py", line 711, in do_expr return do_fcall(v, args, info) File "./esq.py", line 756, in do_fcall return func(*args) TypeError: int() argument must be a string or a number, not 'list' --- arr_memb.txt 2016-09-28 05:14:22.000000000 +0900 +++ arr_memb.es.txt 2016-09-28 05:14:22.000000000 +0900 @@ -1,4 +1,2 @@ c.a=1 -c.b[0]=2 -c.b[1]=3 -c.b[2]=4 +c.b[0]= \ No newline at end of file NG $ arr_memb.c の段階でコケてました。 で、est.py の struct_arr.c +Err alredy exist 'sdef@guha' NG となると @@ -617,6 +621,7 @@ def bind_body_val(lst, vals): def do_type_struct(e, val, info): # [ 'struct', name, body ] + do_sdef(e, info) (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) この修正がまずかった? 呼出しは do_type() do_type_val() do_type_arr() do_type_val() do_type_struct() do_sdef() ;;; ここで初回登録されて do_type_val() do_type_struct() do_sdef() ;;; ここで既に登録されていると.. なーるほど。 構造体の名前と body なんて、一致してたら何回定義しててもいいでしょう。 既に「違う内容で」登録あった場合だけエラーにしておきます。
@@ -685,7 +685,13 @@ def do_sdef(e, info): # [ 'struct', name, body ] (name, body) = e[1:3] if name and body: - new_val( sdef_name(name), body, info ) + name = sdef_name(name) + env = get_call_inf(info).get('env') + if name not in env: + new_val(name, body, info) + return + if env.get(name) != body: + err_exit("already other body exist '{}'".format(name)) def do_expr(expr, info): if info.get('verb') >= 2:
それでは実行。
$ cp est.py esu.py $ cat esu.py-diff.txt | patch $ cat struct_arr.c lib4.c | ./esu.py - c[0].a=1 c[0].b=2 c[1].a=3 c[1].b=4 c[2].a=5 c[2].b=6 $ $ ./test.sh esu.py arr arr2 struct struct2 struct3 arr_memb struct_arr $ テストクリアです。
関数の引数に構造体を渡せるでしょうか? 試してみましょう。
例によってます簡単なプログラムで確かめてみます。
$ cat struct_arg.c #include "lib5.h" struct foo { int x, y; }; struct bar { int a, b, c; }; int hoge(struct foo f, struct bar b) { return f.x + f.y + b.a + b.b + b.c; } int main(int ac, char **av) { struct foo x1 = { 1, 2 }; struct bar x2 = { 3, 4, 5 }; int i = 1; if (i < ac) x1.x = atoi( av[i++] ); if (i < ac) x1.y = atoi( av[i++] ); if (i < ac) x2.a = atoi( av[i++] ); if (i < ac) x2.b = atoi( av[i++] ); if (i < ac) x2.c = atoi( av[i++] ); show_int( hoge(x1, x2) ); show_nl(); return 0; } $ $ gcc -o struct_arg struct_arg.c lib5.c -lm $ ./struct_arg 15 $ ./struct_arg 100 114 $ ./struct_arg 100 200 312 $ ./struct_arg 100 200 300 609 $ ./struct_arg 100 200 300 400 1005 $ ./struct_arg 100 200 300 400 500 1500 $ などと構造体を値渡しで関数に渡してみます。
gcc で正常に動いているので、それでは早速テスト用のスクリプトに追加。
$ cat test2.sh #!/bin/bash ES=$1 chk() { NM=$1 LIB=$2 PRM=$3 echo $NM $PRM if [ "$LIB" = "-" ]; then LIB="" fi if [ "$LIB" = "" ]; then gcc -o $NM $NM.c -lm ./$NM $PRM ; echo $? > $NM.txt else gcc -o $NM $NM.c $LIB.c -lm ./$NM $PRM > $NM.txt fi rm $NM if [ "$LIB" = "" ]; then cat $NM.c | ./$ES - $PRM ; echo $? > $NM.es.txt else cat $NM.c $LIB.c | ./$ES - $PRM > $NM.es.txt fi diff -u -L gcc -L $NM $NM.txt $NM.es.txt || { echo NG ; exit 1; } rm $NM.txt $NM.es.txt } rm -f empty.c touch empty.c chk uhyo chk if chk if - a chk if - "a b" chk if - "a b c" chk sum chk a3 chk a4 chk a10 chk for2 chk global chk global - g chk sin2 empty chk sin3 lib chk san chk san - a chk san - "a b" chk san - "a b c" chk san - "a b c d" chk arr chk arr2 chk struct chk struct2 chk struct3 lib4 chk arr_memb lib4 chk struct_arr lib4 chk struct_arg lib5 chk struct_arg lib5 100 chk struct_arg lib5 "100 200" chk struct_arg lib5 "100 200 300" chk struct_arg lib5 "100 200 300 400" chk struct_arg lib5 "100 200 300 400 500" echo OK rm -f empty.c # EOF $ $ diff -u -L before -L after test.sh test2.sh --- before +++ after @@ -5,29 +5,54 @@ chk() { NM=$1 LIB=$2 + PRM=$3 - echo $NM + echo $NM $PRM + if [ "$LIB" = "-" ]; then + LIB="" + fi if [ "$LIB" = "" ]; then - gcc -o $NM $NM.c - ./$NM ; echo $? > $NM.txt + gcc -o $NM $NM.c -lm + ./$NM $PRM ; echo $? > $NM.txt else - gcc -o $NM $NM.c $LIB.c - ./$NM > $NM.txt + gcc -o $NM $NM.c $LIB.c -lm + ./$NM $PRM > $NM.txt fi rm $NM if [ "$LIB" = "" ]; then - cat $NM.c | ./$ES - ; echo $? > $NM.es.txt + cat $NM.c | ./$ES - $PRM ; echo $? > $NM.es.txt else - cat $NM.c $LIB.c | ./$ES - > $NM.es.txt + cat $NM.c $LIB.c | ./$ES - $PRM > $NM.es.txt fi - #echo hoge >> $NM.es.txt - diff -u $NM.txt $NM.es.txt || { echo NG ; exit 1; } + diff -u -L gcc -L $NM $NM.txt $NM.es.txt || { echo NG ; exit 1; } rm $NM.txt $NM.es.txt } +rm -f empty.c +touch empty.c + +chk uhyo +chk if +chk if - a +chk if - "a b" +chk if - "a b c" +chk sum +chk a3 +chk a4 +chk a10 +chk for2 +chk global +chk global - g +chk sin2 empty +chk sin3 lib +chk san +chk san - a +chk san - "a b" +chk san - "a b c" +chk san - "a b c d" chk arr chk arr2 chk struct @@ -35,5 +60,15 @@ chk struct3 lib4 chk arr_memb lib4 chk struct_arr lib4 +chk struct_arg lib5 +chk struct_arg lib5 100 +chk struct_arg lib5 "100 200" +chk struct_arg lib5 "100 200 300" +chk struct_arg lib5 "100 200 300 400" +chk struct_arg lib5 "100 200 300 400 500" + +echo OK + +rm -f empty.c # EOF $ コマンド引数がある場合も試せるように改造して、 せっかくなので歴代の簡単な確認プログラムで、 試せそうなものは全部追加してみてます。 $ ./test2.sh esu.py uhyo if if a if a b if a b c sum a3 a4 a10 for2 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 global global g sin2 sin3 san san a san a b san a b c san a b c d arr arr2 struct struct2 struct3 arr_memb struct_arr struct_arg struct_arg 100 struct_arg 100 200 struct_arg 100 200 300 struct_arg 100 200 300 400 struct_arg 100 200 300 400 500 OK $
一発とおしですね。
でも知ってます。
構造体の値渡しなのにこのインタプリタでは、関数の中でメンバ変数の値を変更すると、 呼び元側のメンバ変数も更新されてしまうはずです。
このインタプリタでの構造体の実態は python の辞書なので、 構造体の値と思って渡しているのは python の辞書オブジェクトです。
値渡しに見えても C++ の参照渡しのように動作してしまうはずです。
いや、でも、C言語で構造体の値渡しを使うとコピーのコストもあるので、 実際によく目にするのはむしろ構造体のポインタ渡しでしょう。
実はこのインタプリタ、既に構造体のポインタ渡しでもうまく動作してくれないだろうか?
例えば関数定義の引数の箇所は - - fdef - hoge - - br_s - ( - - - type - [struct, foo, null] - [name, f] - [op, ','] - - type - [struct, bar, null] - [name, b] : もし構造体のポインタに変更したとしたら - - fdef - hoge - - br_s - ( - - - type - [struct, foo, null] - [op, '*'] - [name, f] - [op, ','] - - type - [struct, bar, null] - [op, '*'] - [name, b] : 関数定義の引数の箇所は 演算子のまとめ tree_op() 型のまとめ tree_type() でノータッチなので、こうなるはずです。 (引数でなくて定義本体の body の箇所しか再帰呼び出しをかけていません) この情報を使う関数呼出しの処理の箇所では def do_fcall(name, args, info): : (args_info, ret_type, body) = fdef[2:] (_, _, args_lst) = args_info types = [ e[1] for e in args_lst if e[0] == 'type' ] names = [ e[1] for e in args_lst if e[0] == 'name' ] args = [ try_int(t, v) for (t, v) in zip(types, args) ] : なんかうまい具合にサクッと [ op, '*' ] が無視されてる様子。 そして、関数の中のメンバを参照する演算子も def do_op2(v, a, b, info): oa = a a = do_expr(a, info) if v == '?': return do_expr(b[2] if a else b[3], info) if v in [ '.', '->' ]: return a.get( b[1] ) # b == [ name, xxx ] b = do_expr(b, info) if v == '[': return a[ int(b) ] if v == '=': return do_set(oa, b, info) : '.' も '->' も、とりあえず同じ処理になってます。 左辺値に来た場合は def do_set(e, val, info): (k, v) = e[:2] if k == 'name': set_val(v, val, info) return val if e[:2] in [ [ 'op', '.' ], [ 'op', '->' ] ]: d = do_expr( e[2], info ) memb = e[3][1] # [ 'name', xxx ] d[memb] = val return val : これまた '.' と '->' は同じ処理にしてます。
ひょっとしていけるのでは?
呼び元も更新されるので構造体のポインタ渡しの方が理にかなってるのでは?
$ cat struct_arg_ptr.c #include "lib5.h" struct foo { int x, y; }; struct bar { int a, b, c; }; int hoge(struct foo *f, struct bar *b) { return f->x + f->y + b->a + b->b + b->c; } int main(int ac, char **av) { ; struct foo x1 = { 1, 2 }; struct bar x2 = { 3, 4, 5 }; int i = 1; if (i < ac) x1.x = atoi( av[i++] ); if (i < ac) x1.y = atoi( av[i++] ); if (i < ac) x2.a = atoi( av[i++] ); if (i < ac) x2.b = atoi( av[i++] ); if (i < ac) x2.c = atoi( av[i++] ); show_int( hoge(&x1, &x2) ); show_nl(); return 0; } $ diff -u -L before -L after struct_arg.c struct_arg_ptr.c --- before +++ after @@ -3,9 +3,9 @@ struct foo { int x, y; }; struct bar { int a, b, c; }; -int hoge(struct foo f, struct bar b) +int hoge(struct foo *f, struct bar *b) { - return f.x + f.y + b.a + b.b + b.c; + return f->x + f->y + b->a + b->b + b->c; } int main(int ac, char **av) @@ -20,6 +20,6 @@ if (i < ac) x2.b = atoi( av[i++] ); if (i < ac) x2.c = atoi( av[i++] ); - show_int( hoge(x1, x2) ); show_nl(); + show_int( hoge(&x1, &x2) ); show_nl(); return 0; } $ $ gcc -o struct_arg_ptr struct_arg_ptr.c lib5.c -lm $ ./struct_arg_ptr 15 $ $ cat struct_arg_ptr.c lib5.c | ./esu.py - Warn do_op1() not support v='&' Warn do_op1() not support v='&' Traceback (most recent call last): File "./esu.py", line 843, inrcode = do_fcall( 'main', args, info ) File "./esu.py", line 776, in do_fcall do_blk(name, env, body, info) File "./esu.py", line 739, in do_blk do_expr(expr, info) File "./esu.py", line 720, in do_expr args = do_comma( args[0], info ) File "./esu.py", line 604, in do_comma return [ do_expr(e, info) for e in lst ] File "./esu.py", line 721, in do_expr return do_fcall(v, args, info) File "./esu.py", line 776, in do_fcall do_blk(name, env, body, info) File "./esu.py", line 739, in do_blk do_expr(expr, info) File "./esu.py", line 701, in do_expr return do_kwd(v, expr, info) File "./esu.py", line 557, in do_kwd r = do_expr( expr[2], info ) File "./esu.py", line 716, in do_expr return do_op( v, expr[2:], info ) File "./esu.py", line 551, in do_op return do_op2(v, args[0], args[1], info) File "./esu.py", line 464, in do_op2 a = do_expr(a, info) File "./esu.py", line 716, in do_expr return do_op( v, expr[2:], info ) File "./esu.py", line 551, in do_op return do_op2(v, args[0], args[1], info) File "./esu.py", line 464, in do_op2 a = do_expr(a, info) File "./esu.py", line 716, in do_expr return do_op( v, expr[2:], info ) File "./esu.py", line 551, in do_op return do_op2(v, args[0], args[1], info) File "./esu.py", line 464, in do_op2 a = do_expr(a, info) File "./esu.py", line 716, in do_expr return do_op( v, expr[2:], info ) File "./esu.py", line 551, in do_op return do_op2(v, args[0], args[1], info) File "./esu.py", line 464, in do_op2 a = do_expr(a, info) File "./esu.py", line 716, in do_expr return do_op( v, expr[2:], info ) File "./esu.py", line 551, in do_op return do_op2(v, args[0], args[1], info) File "./esu.py", line 468, in do_op2 return a.get( b[1] ) # b == [ name, xxx ] AttributeError: 'NoneType' object has no attribute 'get' $ 残念。 Warn do_op1() not support v='&' アドレスを取る演算子'&'は未実装でした。
@@ -456,6 +456,8 @@ def do_op1(v, a, info): if v == ('--', 'back'): do_set(oa, a-1, info) return a + if v == '&': + return a # !!! warn_no_sup('do_op1', 'v', v) アドレスをとる単項演算子の処理です。 何もしません :-p)
それでは実行。
$ cp esu.py esv.py $ cat esv.py-diff.txt | patch $ cat struct_arg_ptr.c lib5.c | ./esv.py - 15 $ cat struct_arg_ptr.c lib5.c | ./esv.py - 100 200 300 609 $ オッケーです。
テスト用のスクリプトに追加しておきます。
$ diff -u -L test2.sh -L test3.sh test2.sh test3.sh --- test2.sh +++ test3.sh @@ -66,6 +66,12 @@ chk struct_arg lib5 "100 200 300" chk struct_arg lib5 "100 200 300 400" chk struct_arg lib5 "100 200 300 400 500" +chk struct_arg_ptr lib5 +chk struct_arg_ptr lib5 100 +chk struct_arg_ptr lib5 "100 200" +chk struct_arg_ptr lib5 "100 200 300" +chk struct_arg_ptr lib5 "100 200 300 400" +chk struct_arg_ptr lib5 "100 200 300 400 500" echo OK $ $ ./test3.sh esv.py uhyo if if a if a b if a b c sum a3 a4 a10 for2 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 global global g sin2 sin3 san san a san a b san a b c san a b c d arr arr2 struct struct2 struct3 arr_memb struct_arr struct_arg struct_arg 100 struct_arg 100 200 struct_arg 100 200 300 struct_arg 100 200 300 400 struct_arg 100 200 300 400 500 struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 OK $
構造体のポインタ渡し、クリアです。
構造体のポインタが何とかなったということで、 エスケープなデモでも使ってみましょう。
$ cat sin7.c $ diff -up -L 6 -L 7 sin6.c sin7.c --- 6 +++ 7 @@ -1,4 +1,7 @@ -#include "lib4.h" +#include "lib5.h" + +struct xy { int x, y; }; +struct dxy { double x, y; }; int w = 80; int h = 25; @@ -9,22 +12,29 @@ int val(double hz, double sec, int n) return ( sin(a) + 1 ) * 0.5 * n; } -void step(double hz_x, double hz_y, double sec, double prev) +void val_xy(struct dxy *hz, double sec, double add, struct xy *p) { - int x = val(hz_x, sec, w); - int y = val(hz_y, sec, h); - int ox = val(hz_x, prev, w); - int oy = val(hz_y, prev, h); - if (x == ox && y == oy) return; - show_xy_str(ox, oy, " "); - show_xy_str(x, y, "@"); + double sec2 = sec + add; + p->x = val(hz->x, sec2, w); + p->y = val(hz->y, sec2, h); +} + +void step(struct dxy *hz, double sec, double prev, double add) +{ + struct xy p, o; + + val_xy(hz, sec, add, &p); + val_xy(hz, prev, add, &o); + if (p.x == o.x && p.y == o.y) return; + show_xy_str(o.x, o.y, " "); + show_xy_str(p.x, p.y, "@"); } int main(int ac, char **av) { int n = 5, m = 3, i, j, run_sec = 10; double hz = 100; - struct { double x, y; } hzs[] = { { 0.23, 0.49 }, { 0.33, 0.19 }, { 0.41, 0.31 } }; + struct dxy hzs[] = { { 0.23, 0.49 }, { 0.33, 0.19 }, { 0.41, 0.31 } }; double t = 1.0 / hz, ela, nt = 1; double sta = sys_sec(), sec, prev = 0; @@ -37,7 +47,7 @@ int main(int ac, char **av) for (i=0; i<m; i++){ for (j=0; j<n; j++){ double add = nt * j / n; - step(hzs[i].x, hzs[i].y, sec + add, prev + add); + step(&hzs[i], sec, prev, add); } } ela = sec - prev; $ $ gcc -o sin7 sin7.c lib5.c $ ./sin7 いつもの表示ですね。 $ cat sin7.c lib5.c | ./esv.py - : name: main - env: {} name: null - env: {} name: null - env: '@order': [add] add: 0.0 name: null - env: '@order': [p, o] hz: 0.0 o: '@order': [x, y] x: null y: null p: '@order': [x, y] x: null y: null sec: 0.0 name: step flags: [] genv: '@order': [sdef@xy, sdef@dxy, w, h, stdout_write, stdout_flush, sys_int, M_PI, sin, sys_sec, sys_sleep, 'NULL'] M_PI: 3.141592653589793 'NULL': 0.0 h: 25 sdef@dxy: - br_s - '{' - - - type - [double] - - op - ',' - [name, x] - [name, y] - [etc, ;] sdef@xy: - br_s - '{' - - - type - [int] - - op - ',' - [name, x] - [name, y] - [etc, ;] sin: !!python/name:math.sin '' stdout_flush: !!python/name:None.flush '' stdout_write: !!python/name:None.write '' sys_int: !!python/name:__builtin__.int '' sys_sec: !!python/name:time.time '' sys_sleep: !!python/name:time.sleep '' w: 80 lst: '...' verb: 0 Err not found add $ ダメでした。 Err not found add で終了ですが、直前のコールスタックは : name: main - env: {} name: null - env: {} name: null - env: '@order': [add] add: 0.0 name: null - env: '@order': [p, o] hz: 0.0 o: '@order': [x, y] x: null y: null p: '@order': [x, y] x: null y: null sec: 0.0 name: step flags: [] genv: : main関数の最も内側のループのブロックで、変数 add がローカル変数としてスタックに積まれてます。 for (j=0; j<n; j++){ double add = nt * j / n; step(&hzs[i], sec, prev, add); } そして step() の呼出しでその add を引数で渡してるはずですが、 void step(struct dxy *hz, double sec, double prev, double add) { struct xy p, o; : の引数の名前に対して スタック上の step() の env には hz, o, p, sec しか見当たりません。 確かに add がなく、prev もありません。 なぜ? step(&hzs[i], sec, prev, add); この step() 呼び出しの形式は? $ cat sin7.c lib5.c | ./esv.py -vvv - : - - fcall - step - - br_s - ( - - - op - ',' - - op - '&' - - op - ',' - - op - ',' - - op - '[' - [name, hzs] - - br_s - ( - - [name, i] - [name, sec] - [name, prev] - [name, add] - [etc, ;] : ああ! 単項演算子 '&' の優先度が 二項演算子 ',' よりも低い扱いになって...? いや、最後の ',' よりは高く、その前の ',' に負けてる様子? &hzs[i], sec, prev, add これに対して、正解は ( ( ( & ( hzs '[' (i) ) ) , sec ) , prev ) , add のはずですが、現状は ( & ( ( ( hzs '[' (i) ) ) , sec ) , prev ) ) , add
これって以前に二項演算子で似たような事がありました。
見つけにくいバグ のときに関数 op2() を導入して対処したはずです。
確かそう。 +def op2(ops, e, k1, v1, r0): + (k2, v2) = r0[:2] + if k2 == 'op': + i1 = ops_idx(ops, 2, v1) + i2 = ops_idx( ops, len( r0[2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + r02 = op2( ops, e, k1, v1, r0[2] ) + return [ k2, v2, r02, r0[3] ] + return [ k1, v1, e, r0 ] + def tree_op2(lst, ops, e, k1, v1): r = tree_op( lst[2:], ops ) if len(r) == 0: err_exit("not found right term op='{}'".format(v1)) - (k2, v2) = r[0][:2] - if k2 == 'op': - i1 = ops_idx(ops, 2, v1) - i2 = ops_idx( ops, len( r[0][2:] ), v2 ) - if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): - return [ [ k2, v2, [ k1, v1, e, r[0][2] ], r[0][3] ] ] + r[1:] - return [ [ k1, v1, e, r[0] ] ] + r[1:] + return [ op2( ops, e, k1, v1, r[0] ) ] + r[1:] これ、このように。 で、現状の tree_op1() は def tree_op1(lst, ops, k, v): r = tree_op( lst[1:], ops ) if len(r) == 0: err_not_found("rignt term op='{}'".format(v)) (k2, v2) = r[0][:2] if k2 == 'op': i1 = ops_idx(ops, 1, v) i2 = ops_idx( ops, len( r[0][2:] ), v2 ) if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] return [ [ k, v, r[0] ] ] + r[1:] ああ。 if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] ここ。同じ問題です。 再帰してません。
対処しましょう。
@@ -140,17 +140,21 @@ def ops_idx(ops, term, op): err_not_found("term={} op='{}' in ops".format(term, op)) return i +def op1(ops, k, v, r0): + (k2, v2) = r0[:2] + if k2 == 'op': + i1 = ops_idx(ops, 1, v) + i2 = ops_idx( ops, len( r0[2:] ), v2 ) + if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): + r02 = op1( ops, k, v, r0[2] ) + return [ k2, v2, r02 ] + r0[3:] + return [ k, v, r0 ] + def tree_op1(lst, ops, k, v): r = tree_op( lst[1:], ops ) if len(r) == 0: err_not_found("rignt term op='{}'".format(v)) - (k2, v2) = r[0][:2] - if k2 == 'op': - i1 = ops_idx(ops, 1, v) - i2 = ops_idx( ops, len( r[0][2:] ), v2 ) - if i1 < i2 or (i1 == i2 and ops[i1][1] == '>'): - return [ [ k2, v2, [ k, v, r[0][2] ] ] + r[0][3:] ] + r[1:] - return [ [ k, v, r[0] ] ] + r[1:] + return [ op1( ops, k, v, r[0] ) ] + r[1:] def op2(ops, e, k1, v1, r0): (k2, v2) = r0[:2] すごくデジャブです。 op2()と同様にop1()追加です。
それでは実行。
$ cp esv.py esw.py $ cat esw.py-diff.txt | patch $ cat sin7.c lib5.c | ./esw.py - よかった。いつもの表示が出ました。 一応テストも通しておきます。 $ ./test3.sh esw.py uhyo if if a if a b if a b c sum a3 a4 a10 for2 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 global global g sin2 sin3 san san a san a b san a b c san a b c d arr arr2 struct struct2 struct3 arr_memb struct_arr struct_arg struct_arg 100 struct_arg 100 200 struct_arg 100 200 300 struct_arg 100 200 300 400 struct_arg 100 200 300 400 500 struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 OK $
めでたしめでたし。
以前に 型のところをまとめ で こんな事を書いてました。
: unsigned v = 1; unsigned char **u;
kdic の type に unsigned を追加して実行すると、きっとこうなるでしょう。
- [type, unsigned] - [name, v] - [op, '='] - [num, '1'] - [etc, ;] - [type, unsigned] - [type, char] - [op, '*'] - [op, '*'] - [name, u] - [etc, ;] :
未だ 'unsigned' の追加をしてなかったので、やってみましょう。
@@ -324,7 +324,7 @@ def es_split(s): 'br_s' : [ '(', '[', '{' ], 'br_e' : [ ')', ']', '}' ], 'op' : ops_flat, - 'type' : [ 'int', 'char', 'void', 'double' ], + 'type' : [ 'int', 'char', 'void', 'double', 'unsigned' ], 'kwd' : [ 'return', 'if', 'else', 'while', 'for', 'break', 'continue', 'struct' ], 'spc' : [ ' ', '\t', '\n' ], 'etc' : [ ';' ], 単なる kdic への追加です。
適当にターゲットプログラムをでっちあげますると。
$ cat unsigned_ptr.c #include "lib5.h" void foo(unsigned int *a) { show_int(*a); show_nl(); } unsigned int bar_v; unsigned int *bar(unsigned int a) { bar_v = a; return &bar_v; } unsigned int **hoge(unsigned int **a) { return a; } int main(int ac, char **av) { unsigned int x = 123; unsigned int *p; unsigned int **pp; foo(&x); p = bar(456); show_int(*p); show_nl(); pp = hoge(&p); show_int(**pp); show_nl(); return 0; } $ $ gcc -o unsigned_ptr unsigned_ptr.c lib5.c -lm $ ./unsigned_ptr 123 456 456 $ 変数宣言や関数の引数での '*' は、例えば [ type, [ unsigned, int *, * ], xxx ] などとかかえこまれて、 結局 sturct かそうでないかくらいしか見てないので、 ポインタも何もなく、実体の値として扱うはずですよね。 お試しをば。 $ cp esw.py esx.py $ cat esx.py-diff.txt | patch $ cat unsigned_ptr.c lib5.c | ./esx.py - Warn do_op1() not support v='*' None Warn do_op1() not support v='*' None Warn do_op1() not support v='*' Warn do_op1() not support v='*' None $ ああ、アドレスをとる単項演算子 '&' は「何もしない」処理を追加しましたが、 '*' の方は未だでした。
@@ -460,7 +460,7 @@ def do_op1(v, a, info): if v == ('--', 'back'): do_set(oa, a-1, info) return a - if v == '&': + if v in [ '&', '*' ]: return a # !!! warn_no_sup('do_op1', 'v', v) いつかボロが出る悪行です。
$ cp esx.py esy.py $ cat esy-diff.txt | patch $ cat unsigned_ptr.c lib5.c | ./esy.py - 123 456 456 $ いいのだろうか? $ cat unsigned_ptr.c lib5.c | ./esy.py -vvv - --- lst --- - - fdef - foo - - br_s - ( - - - type - - - [unsigned, int] - '*' - [name, a] - - type - [void] - - br_s - '{' : - - type - - [unsigned, int] - [name, bar_v] - [etc, ;] - - fdef - bar - - br_s - ( - - - type - - [unsigned, int] - [name, a] - - type - - - [unsigned, int] - '*' - - br_s - '{' - - - op - '=' - [name, bar_v] - [name, a] - [etc, ;] - - kwd - return - - op - '&' - [name, bar_v] - [etc, ;] - - fdef - hoge - - br_s - ( - - - type - - - - [unsigned, int] - '*' - '*' - [name, a] - - type - - - - [unsigned, int] - '*' - '*' - - br_s - '{' - - - kwd - return - [name, a] - [etc, ;] - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] - - type - [int] - - br_s - '{' - - - type - - [unsigned, int] - - op - '=' - [name, x] - [num, '123'] - [etc, ;] - - type - - - [unsigned, int] - '*' - [name, p] - [etc, ;] - - type - - - - [unsigned, int] - '*' - '*' - [name, pp] - [etc, ;] : 問題あるけど、実行には問題なかったみたいです。 - - - type - - - [unsigned, int] - '*' - [name, a] とか - - - type - - - - [unsigned, int] - '*' - '*' - [name, a] とか - - - type - [int] - [name, ac] - [op, ','] - - type - - - [char, '*'] - '*' - [name, av] とか - - type - - - - [unsigned, int] - '*' - '*' - [name, pp] - [etc, ;] ずいぶん以前の 型のところをまとめ : - [type, unsigned] - [type, char] - [op, '*'] - [op, '*'] - [name, u] - [etc, ;] : は join_type() 実行後なのでフラットな形式ですが - - type - - - - [unsigned, int] - '*' - '*' - [name, pp] - [etc, ;] これはさすがに - - type - [unsigned, int, '*', '*' ] - [name, pp] - [etc, ;] こうなるつもりでした。 修正すべしです。
@@ -76,8 +76,7 @@ def join_type(top, lst): return [ to_len2(top) ] + lst if i > 0: return [ to_len2(top) ] + lst[:i] + join_type( None, lst[i:] ) - top += [ lst[0][1] ] - return join_type( to_len2(top), lst[1:] ) + return join_type( top + [ lst[0][1] ], lst[1:] ) def tree_bra(lst, kdic, sta=None): d = dict( zip( kdic.get('br_s'), kdic.get('br_e') ) ) 余計な to_len2() 呼び出しが入ってたので削除しておきます。
それでは実行。
$ cp esy.py esz.py $ cat esz-diff.txt | patch $ cat unsigned_ptr.c lib5.c | ./esz.py - 123 456 456 $ そして $ cat unsigned_ptr.c lib5.c | ./esz.py -vvv - - - - type - [unsigned, int, '*'] - [name, a] - - type - [void] - - br_s - '{' - - - fcall - show_int - - br_s - ( - - - op - '*' - [name, a] - [etc, ;] - - fcall - show_nl - - br_s - ( - [] - [etc, ;] - - type - [unsigned, int] - [name, bar_v] - [etc, ;] - - fdef - bar - - br_s - ( - - - type - [unsigned, int] - [name, a] - - type - [unsigned, int, '*'] - - br_s - '{' - - - op - '=' - [name, bar_v] - [name, a] - [etc, ;] - - kwd - return - - op - '&' - [name, bar_v] - [etc, ;] - - fdef - hoge - - br_s - ( - - - type - [unsigned, int, '*', '*'] - [name, a] - - type - [unsigned, int, '*', '*'] - - br_s - '{' - - - kwd - return - [name, a] - [etc, ;] - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - [char, '*', '*'] - [name, av] - - type - [int] - - br_s - '{' - - - type - [unsigned, int] - - op - '=' - [name, x] - [num, '123'] - [etc, ;] - - type - [unsigned, int, '*'] - [name, p] - [etc, ;] - - type - [unsigned, int, '*', '*'] - [name, pp] - [etc, ;] : そう。 - - type - [unsigned, int, '*', '*'] - [name, pp] - [etc, ;]
こうしたかったのでした。
例によってテストに追加して通しておきましょう。
$ diff -u -L 3 -L 4 test3.sh test4.sh --- 3 +++ 4 @@ -72,6 +72,7 @@ chk struct_arg_ptr lib5 "100 200 300" chk struct_arg_ptr lib5 "100 200 300 400" chk struct_arg_ptr lib5 "100 200 300 400 500" +chk unsigned_ptr lib5 echo OK $ $ ./test4.sh esz.py | tail struct_arg 100 200 300 400 struct_arg 100 200 300 400 500 struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 unsigned_ptr OK $
関数で構造体の値を返せるか試してみます。
$ cat struct_ret.c #include "lib5.h" struct xy { int x, y; }; struct xy foo(int x, int y) { struct xy r = { x, y }; return r; } void show_xy(struct xy *a) { show_str("["); show_int(a->x); show_str(", "); show_int(a->y); show_str("]"); } int main(int ac, char **av) { int x = opt_int("-x", ac, av, 0); int y = opt_int("-y", ac, av, 0); struct xy a = foo(x, y); show_xy(&a); show_nl(); return 0; } $ $ gcc -o struct_ret struct_ret.c lib5.c -lm $ ./struct_ret [0, 0] $ ./struct_ret -x 123 -y 456 [123, 456] してからに。 $ cat struct_ret.c lib5.c | ./esz.py - Err not found x $ ダメでした。 $ cat struct_ret.c lib5.c | ./esz.py -vvv - --- lst --- - - sdef - - struct - xy - - br_s - '{' - - - type - [int] - - op - ',' - [name, x] - [name, y] - [etc, ;] - [etc, ;] - - type - [struct, xy, null] - - fcall - foo - - br_s - ( - - - type - [int] - - op - ',' - [name, x] - - type - [int] - [name, y] - - br_s - '{' - - - type - [struct, xy, null] - - op : むむ。 struct xy { int x, y; }; 構造体定義のあと struct xy foo(int x, int y) { struct xy r = { x, y }; return r; } この関数定義が - - type - [struct, xy, null] - - fcall - foo - - br_s - ( - - - type - [int] - - op - ',' - [name, x] - - type - [int] - [name, y] なので [ type, [struct, xy, null], [ [ fcall, foo, ... になってて、変数宣言として扱われつつも 変数名のあたりが関数呼び出しとして認識されてます。 全然ダメ。 でもなぜ? int を返す main関数は - - fdef - main - - br_s - ( - - - type - [int] - [name, ac] - [op, ','] - - type - [char, '*', '*'] - [name, av] - - type - [int] - - br_s - '{' : ちゃんと関数定義として [ fdef, main, ... ] となっています。 es_split() の処理の途中経過を確認してみますかね。
って、またファイル名が枯渇です。
とりあえず es_a.py で。
@@ -271,7 +271,7 @@ def tree_type(lst): return step(e, 2) return step(e, 1) -def es_split(s): +def es_split(s, verb): s = s.replace('@', '@ ') ''' @@ -356,10 +356,16 @@ def es_split(s): lst = [ try_num_name(e, e[:2]) for e in lst ] lst = join_type(None, lst) + if verb >= 4: + show_yaml(lst, 'join_type') lst = tree_bra(lst, kdic) + if verb >= 4: + show_yaml(lst, 'tree_bra') lst = name_bra(lst) + if verb >= 4: + show_yaml(lst, 'name_bra') ops[0][2].append('[') # !!! @@ -831,12 +837,12 @@ if __name__ == "__main__": else: verb = 0 argv = sys.argv[:] - for (i, opt) in enumerate( [ '-v', '-vv', '-vvv' ] ): + for (i, opt) in enumerate( [ '-v', '-vv', '-vvv', '-vvvv' ] ): if opt in argv: argv.remove(opt) verb = i + 1 s = load_src( argv[1] ) - lst = es_split(s) + lst = es_split(s, verb) if verb >= 3: show_yaml(lst, 'lst') '-vvvv' オプションを追加して es_split() の前半で途中経過の表示を追加しています。 $ cp esz.py es_a.py $ cat es_a.py-diff.txt | patch $ cat struct_ret.c lib5.c | ./es_a.py -vvvv - --- join_type --- - [kwd, struct] - [name, xy] - [br_s, '{'] - - type - [int] - [name, x] - [op, ','] - [name, y] - [etc, ;] - [br_e, '}'] - [etc, ;] - [kwd, struct] - [name, xy] - [name, foo] - [br_s, (] - - type - [int] : 表示が大量にでますが、関数 foo の定義あたりを抜粋すると : --- tree_bra --- - [kwd, struct] - [name, xy] - - br_s - '{' - - - type - [int] - [name, x] - [op, ','] - [name, y] - [etc, ;] - [etc, ;] - [kwd, struct] - [name, xy] - [name, foo] - - br_s - ( - - - type - [int] : ああ、ここで foo の前に[kwd, struct] そして、その結果 : --- name_bra --- - [kwd, struct] - [name, xy] - - br_s - '{' - - - type - [int] - [name, x] - [op, ','] - [name, y] - [etc, ;] - [etc, ;] - [kwd, struct] - [name, xy] - - fcall - foo - - br_s - ( - - - type - [int] - [name, x] - [op, ','] - - type - [int] - [name, y] - - br_s - '{' : ああ、 [ type, ... ], [ name, ... ], [ br_s, '(', ... ] じゃなかったので関数定義とみなされず [ name, ... ], [ br_s, '(', ... ] でマッチして関数呼び出しと思ってしまってます。 そうか... - [kwd, struct] - [name, xy] はもっと後続の tree_kwd() の処理で [ struct, xy, None ] にまとめられてから 直後に [ 'etc', ';' ] がきてない場合なので [ type, [ struct, xy, None ] ], xxx, ... になるはず 最後に tree_type() で 直後の要素をとりこんで [ type, [ struct, xy, None ] xxx ], ... この xxx が今回 [ fcall, foo, ... ] であると。 なんとなく原因は判りました。
さてどうしたものか...。
int *foo(int a) ... ならば [ type, int ], [ op, * ], [ name, foo ], [ br_s, ( ], [ type, int ], [ name, a ], [ br_s, ) ] ... join_type() で [ type, [ int, * ] ], [ name, foo ], [ br_s, ( ], [ type, [ int ] ], [ name, a ], [ br_s, ) ] ... tree_bra() で [ type, [ int, * ] ], [ name, foo ], [ br_s, (, [ [ type, [ int ] ], [ name, a ] ] ] ... name_bra() で type, name, ( のパターンなので [ fdef, foo, [ br_s, (, [ [ type, [ int ] ], [ name, a ] ] ] [ type [i nt * ] ] ... ] なのですが struct xy foo(int a) ... ならば [ kwd, struct ], [ name, xy ], [ name, foo ], [ br_s, ( ], [ type, int ], [ name, a ], [ br_s, ) ] ... join_type() で [ kwd, struct ], [ name, xy ], [ name, foo ], [ br_s, ( ], [ type, [ int ] ], [ name, a ], [ br_s, ) ] ... : うーむむ。 name_bra() で関数と認識させるには、 引数部分の判定前に tree_bra() 処理は必須。 name_bra() で関数と認識させるには、 関数の返り値として 直前に [ type, ... ] にまとまってる必要があるので、 join_type() 処理は必須。 [ kwd, struct ] から [ type, [ struct ... への変換は後続の tree_kwd() での処理。 現状の順では join_type() tree_bra() name_bra() tree_op() tree_kwd() name_bra() で関数定義として認識させるには、 tree_kwd() 処理して [ kwd, struct ] は [ type, [ struct ... ] ] になっててほしい。 でも tree_kwd() では... [ kwd, retrun ] の処理をはじめとしてtree_op() が終ってる前提になっているし、 [ kwd, if ] や [ kwd, while ] は、'(' や '{' ブロックを扱うので、 tree_bra() も終ってる前提。 そして [ kwd, struct ] では、 後続に構造体定義本体 { ... } の body があるパターンもあって、 やはり tree_bra() は必要。こうして考えると、まず tree_bra() ありきでしょう。 先頭2つの処理順を入れ替えて tree_bra() join_type() name_bra() tree_op() tree_kwd() そして、次に tree_kwd() の一部として [ kwd, struct ] 関連だけ先にまとめて [ sdef, [ struct, ... ] ] または [ type, [ struct, ... ] ] にもっていくとすると... 例えばこの時点では int * と比べると [ type, int ], [ op, * ] に対して [ type, [ struct, name, body ] ], [ op, * ] とかになってるはずで、join_type() の処理で [ type, [ int, * ] ] に対して [ type, [ [ struct, name, body ], * ] ] そして name_bra() で、はれて関数定義の判定... 最後の tree_kwd() で 'struct' の処理から、 前方に移動した内容は削除しておきます。
こんなストーリーでどうでしょうか?
関数で構造体の値を返したいだけなのに、かなりの大手術の予感です。 果たしてうまくいくでしょうか?
いや、アプローチを変えます。
そもそも関数定義を判別するのに、先頭の [ type, ... ] に頼るのがまずいのかも。
先頭の [ type, ... ] の有無で関数呼出しかプロトタイプ宣言かの判別する考えがあって、 だったらプロトタイプ宣言と同様に関数定義もと発想してました。
関数定義だけで考えると、先頭の [ type, ... ] に頼らず、 本体 { ... } の有無で判別したらいいはずです。
name_br() の段階では [ type, ... ] は [ kwd, struct ... ] になってるかもしれないので、 [ type, ... ] による判定は使わないようにします。
プロトタイプ宣言と関数呼出しの判定が難しくなりますが、 この段階ではどちらも関数呼出しとして判定して、プロトタイプ宣言の判定は先送りします。
tree_kwd() で [ kwd, struct ... ] が [ type, ... ] に変わった後、 tree_type() の中では、関数定義もプロトタイプ宣言も、[ type, ... ] 配下に まとめられようとするはずなので、 ここで関数呼出しという仮の姿から、プロトタイプ宣言に修正してやれるはずです。
大手術は避けて、この方針で進めてみます。
と、その前に。
デバッグしやすいように少し変更しておきます。 main 関数定義が無い場合は、es_split() 結果のリストを表示するだけにしておきます。
@@ -833,7 +833,7 @@ def load_src(fn): if __name__ == "__main__": rcode = 0 if len(sys.argv) <= 1: - print('usage: {} [-v -vv -vvv] file'.format(sys.argv[0])) + print('usage: {} [-v -vv -vvv -vvvv] file'.format(sys.argv[0])) else: verb = 0 argv = sys.argv[:] ここは前回の修正モレです。 @@ -843,9 +843,15 @@ if __name__ == "__main__": verb = i + 1 s = load_src( argv[1] ) lst = es_split(s, verb) - if verb >= 3: + + is_main = next( ( True for e in lst if e[:2] == [ 'fdef', 'main' ] ), False ) + + if verb >= 3 or not is_main: show_yaml(lst, 'lst') + if not is_main: + sys.exit(rcode) + info = do_global(lst, verb) argv = argv[1:] main関数定義が無い場合は、es_split() 結果を表示するだけにしています。
これで短いソースコードの解釈を確めやすくなりました。
$ cp es_a.py es_b.py $ cat es_b.py-diff.txt | patch $ ./es_b.py - <<EOL > struct { int x, y; } a = { 1, 2 }; > EOL --- lst --- - - type - - struct - null - - br_s - '{' - - - type - [int] - - op - ',' - [name, x] - [name, y] - [etc, ;] - - op - '=' - [name, a] - - br_s - '{' - - - op - ',' - [num, '1'] - [num, '2'] - [etc, ;] $ $ ( cat - lib5.c | ./es_b.py - ) <<EOL > int main(int ac, char **av){ > int a = 123; > show_int(a); show_str("\n"); > return 0; } > EOL 123 $
ヒアドキュメントの力を借りて、標準入力指定 '-' と組み合わせれば、 短いソースコードを直接入力できるので、インタプリタっぽいですね。
さて、本題。
@@ -100,11 +100,9 @@ def name_bra(lst): if len(lst) == 0: return lst tbl = [ - [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ], [ 'br_s', '{' ] ], - lambda type, name, (k, v, args), (k2, v2, body): - [ [ 'fdef', name[1], [ k, v, name_bra(args) ], type, [ k2, v2, name_bra(body) ] ] ] ], - [ [ [ 'type' ], [ 'name' ], [ 'br_s', '(' ] ], - lambda type, name, (k, v, args): [ [ 'fproto', name[1], [ k, v, name_bra(args) ], type ] ] ], + [ [ [ 'name' ], [ 'br_s', '(' ], [ 'br_s', '{' ] ], + lambda name, (k, v, args), (k2, v2, body): + [ [ 'fdef', name[1], [ k, v, name_bra(args) ], None, [ k2, v2, name_bra(body) ] ] ] ], [ [ [ 'name' ], [ 'br_s', '(' ] ], lambda name, (k, v, args): [ [ 'fcall', name[1], [ k, v, name_bra(args) ] ] ] ], [ [ [ 'name' ], [ 'br_s', '[' ] ], name_bra() で 関数定義にマッチする条件から [ 'type' ] をはずして、 この段階ではプロトタイプ宣言は、判別せずに削除しています。 「仮の姿」として関数呼び出しとして扱います。 @@ -267,7 +265,13 @@ def tree_type(lst): f = lambda s: s[:2] + [ tree_type_br_s( s[2] ) ] e = e[:1] + [ f( e[1] ) ] if e[0] == 'type' and len( lst[1:] ) > 0: - e = e[:2] + [ lst[1] ] + if lst[1][0] == 'fdef': + fdef = tree_type( [ lst[1] ] )[0] + e = fdef[:3] + [ e ] + fdef[4:] + elif lst[1][0] == 'fcall': + e = [ 'fproto' ] + lst[1][1:] + [ e ] + else: + e = e[:2] + [ lst[1] ] return step(e, 2) return step(e, 1) 後続の tree_type() で 関数定義と関数呼び出しを改めて処理するようにしています。
さてこれで如何に。
$ cp es_b.py es_c.py $ cat es_c.py-diff.txt | patch $ cat struct_ret.c #include "lib5.h" struct xy { int x, y; }; struct xy foo(int x, int y) { struct xy r = { x, y }; return r; } void show_xy(struct xy *a) { show_str("["); show_int(a->x); show_str(", "); show_int(a->y); show_str("]"); } int main(int ac, char **av) { int x = opt_int("-x", ac, av, 0); int y = opt_int("-y", ac, av, 0); struct xy a = foo(x, y); show_xy(&a); show_nl(); return 0; } $ いざ。 $ cat struct_ret.c lib5.c | ./es_c.py - Warn do_expr() not support [k, v]='['b', 'r']' [None, None] $ 表示はでつつも謎の警告表示がでました。 はて? $ cat struct_ret.c lib5.c | ./es_c.py -vvv - | fold : expr=['name', 'def_ret'] expr=['etc', ';'] expr=['type', ['struct', 'xy', None], ['op', '=', ['name', 'a'], ['fcall', 'foo' , ['br_s', '(', [['op', ',', ['name', 'x'], ['name', 'y']]]]]]] expr=['type', ['int'], ['op', ',', ['op', '=', ['name', 'x'], 'br_s'], ['name', 'y']]] expr=br_s Warn do_expr() not support [k, v]='['b', 'r']' expr=['etc', ';'] : ん? expr=['type', ['struct', 'xy', None], ['op', '=', ['name', 'a'], ['fcall', 'foo', ['br_s', '(', [ ['op', ',', ['name', 'x'], ['name', 'y']] ]] ] ] ] からの expr=['type', ['int'], ['op', ',', ['op', '=', ['name', 'x'], 'br_s'], ['name', 'y'] ] ] これは... main()関数の struct xy a = foo(x, y); 構造体の変数 a の初期化で struct xy { int x, y; }; の body int x, y; に対して、x だけに初期値 'br_s' ? int x = br_s, y; にして、初期値設定しようとしてる? としたら処理関数は def do_type_struct(e, val, info): # [ 'struct', name, body ] do_sdef(e, info) (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info ) if val is not None: vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] (r, vals) = bind_body_val( body[2], vals ) body = body[:2] + [ r ] benv = {} do_blk(None, benv, body, info) return benv ここ。 ああ! vals = flat_comma( val[2][0] ) # val is [ 'br_s', '{', [ [ 'op', ',' ... ] ] ] 初期化する値 val として { 1, 2 } とかを期待してるけど struct xy a = foo(x, y); 関数呼び出しの結果の値で初期化しようとしてました。
とりあえず安易に対処を入れておきます。
@@ -637,6 +637,10 @@ def bind_body_val(lst, vals): def do_type_struct(e, val, info): # [ 'struct', name, body ] do_sdef(e, info) + + if val is not None and val[:2] != [ 'br_s', '{' ]: + return do_expr(val, info) + (name, body) = e[1:3] if body is None: body = get_val( sdef_name(name), info )
それでは実行。
$ cp es_c.py es_d.py $ cat es_d.py-diff.txt | patch $ cat struct_ret.c lib5.c | ./es_d.py - [0, 0] $ $ cat struct_ret.c lib5.c | ./es_d.py - -x 123 -y 456 [123, 456] $ できました。
例によってテストに追加して通しておきましょう。
$ diff -u -L 4 -L 5 test4.sh test5.sh --- 4 +++ 5 @@ -27,7 +27,7 @@ cat $NM.c $LIB.c | ./$ES - $PRM > $NM.es.txt fi - diff -u -L gcc -L $NM $NM.txt $NM.es.txt || { echo NG ; exit 1; } + diff -u -L gcc -L $ES $NM.txt $NM.es.txt || { echo NG ; exit 1; } rm $NM.txt $NM.es.txt } @@ -73,6 +73,8 @@ chk struct_arg_ptr lib5 "100 200 300 400" chk struct_arg_ptr lib5 "100 200 300 400 500" chk unsigned_ptr lib5 +chk struct_ret lib5 +chk struct_ret lib5 "-x 123 -y 456" echo OK $ $ ./test5.sh es_d.py | tail struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 unsigned_ptr struct_ret struct_ret -x 123 -y 456 OK $
こんなコードの解釈を簡単に確認してみますと
$ ./es_d.py - <<EOL > int *a, b, **c; > EOL --- lst --- - - type - [int, '*'] - - op - ',' - - op - ',' - [name, a] - [name, b] - - op - '*' - - op - '*' - [name, c] - [etc, ;] $ なんか変です。 解釈したリストが表してるのは int *a, b, **c; ではなくて '(' int* ')' a, b, **c; 型情報の中に '*' が入れ込まれて、C++流な風情になってます。 変数 c は int ** なのか? int *** なのか? まぁ '*' の処理は手抜きで何もしてないので実害は無いのですが。
型情報に '*' を含めないように整理しておきます。
@@ -70,7 +70,7 @@ def join_type(top, lst): if top is None: i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) return lst if i is None else lst[:i] + join_type( lst[i], lst[i+1:] ) - i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' or e == [ 'op', '*' ] ), None ) + i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) to_len2 = lambda e: [ e[0], e[1:] ] if i is None: return [ to_len2(top) ] + lst [ type, unsigned ], [ type, int ] は [ type, [ unsigned, int ] ] にしますが [ type, int ], [ op, * ] は [ type, [ int * ] ] にしません。 もうやめにします。 @@ -244,6 +244,11 @@ def tree_kwd(lst, kdic): e = e[:2] + tree_kwd( e[2:], kdic ) return step(e, 1) +def tree_type_ptr_exp(e): + if e[:2] == [ 'op', '*' ]: + return [ '*' ] + tree_type_ptr_exp( e[2] ) + return [ e ] + def tree_type(lst): if len(lst) == 0: return lst @@ -265,6 +270,10 @@ def tree_type(lst): f = lambda s: s[:2] + [ tree_type_br_s( s[2] ) ] e = e[:1] + [ f( e[1] ) ] if e[0] == 'type' and len( lst[1:] ) > 0: + if lst[1][:2] == [ 'op', '*' ]: + exp = tree_type_ptr_exp( lst[1] ) + if exp[-1][0] in [ 'fdef', 'fcall' ]: + return tree_type( [ e + exp[:-1] , exp[-1] ] + lst[2:] ) if lst[1][0] == 'fdef': fdef = tree_type( [ lst[1] ] )[0] e = fdef[:3] + [ e ] + fdef[4:] [ type, ... ] のあとに [ op, *, ... ] がきてたら、 追加した tree_type_ptr_exp() で [ op, *, ... ] がネストしてるようであれば フラットに展開します。 その奥底に関数定義や、関数呼び出し(プロトタイプ宣言の仮の姿)がきてるようであれば、 表にひっぱり出して、関数定義やプロトタイプ宣言側に、 返り値として [ type, ... ] を逆に吸収させます。 けっこうややこしいです。 @@ -628,7 +637,7 @@ def bind_body_val(lst, vals): if e[0] == 'type' or e[:2] == [ 'op', ',' ]: (r, vals) = bind_body_val( e[2:], vals ) e = e[:2] + r - elif e[0] == 'name' or e[:2] == [ 'op' ,'[' ]: + elif e[0] == 'name' or e[:2] in [ [ 'op' ,'[' ], [ 'op', '*' ] ]: e = [ 'op', '=', e, vals[0] ] vals = vals[1:] (r, vals) = bind_body_val( lst[1:], vals ) 構造体の body に初期化用の初期値を追加してまわる処理関数です。 [ op, =, ... ] を挿入する候補がこれまでは 例えば int foo, bar[2]; に対して [ 1, { 2, 3 } ] で初期化するなら int foo = 1, bar[2] = { 2, 3 }; としていました。 ここでポインタも扱えるように追加したので、これからは int foo, bar[2], *hoge; に対して [ 1, { 2, 3 }, fuga() ] で初期化するなら int foo = 1, bar[2] = { 2, 3 }, *hoge = fuga(); として処理されます @@ -692,6 +701,8 @@ def do_type(expr, info): if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] + while e[:2] == [ 'op', '*' ]: + e = e[2] # !!! if e[:2] == [ 'op', '[' ]: arr_n = e[3] e = e[2] この箇所の関数全体は def do_type(expr, info): typ = expr[1] for e in flat_comma( expr[2] ): arr_n = False val = None if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] while e[:2] == [ 'op', '*' ]: e = e[2] # !!! if e[:2] == [ 'op', '[' ]: arr_n = e[3] e = e[2] name = e[1] val = do_type_val(typ, arr_n, val, info) new_val(name, val, info) return None になります。 これまでは例えば unsigned int **a; は [ type, [ unsigned, int, *, * ], [ name, a ] ] でしたが、今回からは [ type, [ unsigned int ], [ op, *, [ op, *, [ name, a ] ] ] ] になります。 [ op, *, [ op, *, [ name, a ] ] ] + while e[:2] == [ 'op', '*' ]: + e = e[2] # !!! 追加したこの処理で [ op, * ] を無視して [ name, a ] をひっぱり出しています。
それでは実行。
$ cp es_d.py es_e.py $ cat es_e.py-diff.txt | patch $ ./es_e.py - <<EOL > int *a, b, **c; > EOL --- lst --- - - type - [int] - - op - ',' - - op - ',' - - op - '*' - [name, a] - [name, b] - - op - '*' - - op - '*' - [name, c] - [etc, ;] $ この解釈で確かに int *a, b, **c; になってます。 例によってテストをかけておきましょう。 $ ./test5.sh es_e.py | tail struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 unsigned_ptr struct_ret struct_ret -x 123 -y 456 OK $
構造体の値を返す関数は無事クリアしましたが、 構造体のポインタを返す場合で引っかかってしまいました。
例えば...
整数 int の場合 $ ./es_e.py - <<EOL > int foo(void){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [void] - - type - [int] - - br_s - '{' - [] $ ./es_e.py - <<EOL > int *foo(void){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [void] - - type - [int] - '*' - - br_s - '{' - [] $ [ fdef, 関数名, 引数, 返り値, 本体 ] の形式で 返り値の箇所だけが [ type, [ int ] ] から [ type, [ int ], * ] に変化してます。 構造体の場合では $ ./es_e.py - <<EOL > struct bar foo(void){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [void] - - type - [struct, bar, null] - - br_s - '{' - [] $ ./es_e.py - <<EOL > struct bar *foo(void){} > EOL --- lst --- - - type - [struct, null, null] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] $ [ fdef, 関数名, 引数, 返り値, 本体 ] の形式で 整数 int の場合の [ type, [ int ] ] から [ type, [ struct, bar, null ] ] は良く ポインタの場合 [ type, [ struct, bar, null ], * ] になって欲しいところが... そもそも関数定義 fdef が外に引っ張りだされておらず、 [ type, ... ] の中に取り込まれたままです。
もちょっと途中経過の詳細を表示してみましょう。
@@ -383,10 +383,16 @@ def es_split(s, verb): ops[0][2].append('[') # !!! lst = tree_op(lst, ops) + if verb >= 4: + show_yaml(lst, 'tree_op') lst = tree_kwd(lst, kdic) + if verb >= 4: + show_yaml(lst, 'tree_kwd') lst = tree_type(lst) + if verb >= 4: + show_yaml(lst, 'tree_type') return lst -vvvv オプションで関数 es_split() の後半にも表示を追加してみました。
では問題のコードで試してみます。
$ cp es_e.py es_f.py $ cat es_f.py-diff.txt | patch $ ./es_f.py -vvvv - <<EOL > struct bar *foo(void){} > EOL --- join_type --- - [kwd, struct] - [name, bar] - [op, '*'] - [name, foo] - [br_s, (] - - type - [void] - [br_e, )] - [br_s, '{'] - [br_e, '}'] --- tree_bra --- - [kwd, struct] - [name, bar] - [op, '*'] - [name, foo] - - br_s - ( - - - type - [void] - - br_s - '{' - [] --- name_bra --- - [kwd, struct] - [name, bar] - [op, '*'] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- tree_op --- - [kwd, struct] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- tree_kwd --- - - type - [struct, null, null] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- tree_type --- - - type - [struct, null, null] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- lst --- - - type - [struct, null, null] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] $ ずらずらっと出ましたが... ここです。 --- name_bra --- - [kwd, struct] - [name, bar] - [op, '*'] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- tree_op --- - [kwd, struct] - - op - '*' - [name, bar] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] なーるほど。 '*' が掛け算として処理されてます。 ちなみに int の場合では $ ./es_f.py -vvvv - <<EOL > int *foo(void){} > EOL : --- name_bra --- - - type - [int] - [op, '*'] - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] --- tree_op --- - - type - [int] - - op - '*' - - fdef - foo - - br_s - ( - - - type - [void] - null - - br_s - '{' - [] : 単項演算子 '*' として扱われてますね。 [ type, ... ], [ op, * ], [ fdef, .. ] なら単項演算子 [ name, ... ], [ op, * ], [ fdef, .. ] なら二項演算子 うむむ... はっ。という事は普通の変数宣言でも? $ ./es_f.py -vvvv - <<EOL > int *foo; > struct bar *hoge; > EOL --- lst --- - - type - [int] - - op - '*' - [name, foo] - [etc, ;] - - type - [struct, null, null] - - op - '*' - [name, bar] - [name, hoge] - [etc, ;] $ あーダメ。
es_e.py-diff.txt の時の
@@ -70,7 +70,7 @@ def join_type(top, lst): if top is None: i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) return lst if i is None else lst[:i] + join_type( lst[i], lst[i+1:] ) - i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' or e == [ 'op', '*' ] ), None ) + i = next( ( i for (i, e) in enumerate(lst) if e[0] == 'type' ), None ) to_len2 = lambda e: [ e[0], e[1:] ] if i is None: return [ to_len2(top) ] + lst [ type, unsigned ], [ type, int ] は [ type, [ unsigned, int ] ] にしますが [ type, int ], [ op, * ] は [ type, [ int * ] ] にしません。 もうやめにします。 :
この対応で int の時は問題なくても、構造体の場合は面倒見切れてない状態でした。
もう一度詳細を $ ./es_f.py -vvvv - <<EOL > struct bar *hoge; > EOL : --- name_bra --- - [kwd, struct] - [name, bar] - [op, '*'] - [name, hoge] - [etc, ;] --- tree_op --- - [kwd, struct] - - op - '*' - [name, bar] - [name, hoge] - [etc, ;] --- tree_kwd --- - - type - [struct, null, null] - - op - '*' - [name, bar] - [name, hoge] - [etc, ;] :
そらー bar カケル hoge に見えるよなー。
さて、どうすべしか...。
すぐ思いつく対応は2つです。
ソースを眺めてみると後者の方が良さそうです。 対応してみましょう。
@@ -195,6 +195,9 @@ def tree_op(lst, ops): return tstk_pop_ret(ret) return tstk_pop_ret( tree_op2(lst, ops, e, k1, v1) ) + if e[:2] == [ 'kwd', 'struct' ] and lst[1][0] == 'name': + return tstk_pop_ret( lst[:2] + tree_op( lst[2:], ops ) ) + return tstk_pop_ret( [ e ] + tree_op( lst[1:], ops ) ) def tree_kwd(lst, kdic): tree_op() の演算子をまとめる処理に手を入れてます。 コーディングしてみると狙い通り少しの変更で済みました。
それでは実行。
$ cp es_f.py es_g.py $ cat es_g.py-diff.txt | patch $ ./es_g.py - <<EOL > struct bar *foo; > EOL --- lst --- - - type - [struct, bar, null] - - op - '*' - [name, foo] - [etc, ;] $ よし。 無事、単項演算子 '*' として扱われるようになりました。 そして、タイトルの構造体のポインタを返す関数の場合 $ ./es_g.py - <<EOL > int *foo(void){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [void] - - type - [int] - '*' - - br_s - '{' - [] $ ちゃんと fdef で関数定義として扱われるようになりました。
後出しではありますが、せっかくなので確認プログラムを作って テストに追加しておきましょう。
さらについでに lib5.c から lib6.c に更新しておきます。
$ diff -up -L5 -L6 lib5.c lib6.c --- 5 +++ 6 @@ -1,4 +1,4 @@ -#include "lib5.h" +#include "lib6.h" /*-->| only interpreter @@ -85,6 +85,19 @@ void show_f(double v) void show_int(int v) { show_v(v, 1); +} + +/*-->| only interpreter +struct xy { int x, y; }; +|<--*/ // only interpreter + +void show_xy(struct xy *a) +{ + show_str("["); + show_int(a->x); + show_str(", "); + show_int(a->y); + show_str("]"); } void show_esc(void) $ $ diff -up -L5 -L6 lib5.h lib6.h --- 5 +++ 6 @@ -1,5 +1,5 @@ -#ifndef __LIB5_H__ -#define __LIB5_H__ +#ifndef __LIB6_H__ +#define __LIB6_H__ #include <sys/time.h> #include <stdio.h> @@ -8,6 +8,8 @@ #include <string.h> #include <unistd.h> +struct xy { int x, y; }; + void stdout_write(char *s); void stdout_flush(void); void show_str(char *s); @@ -16,6 +18,7 @@ void show_spc(int n); void show_v(int v, int by_asc); void show_f(double v); void show_int(int v); +void show_xy(struct xy *a); void show_esc(void); void cls(void); void locate(int x, int y); $ $ cat struct_ret_ptr.c #include "lib6.h" struct xy g = { 123, 456 }; struct xy *foo(void) { return &g; } int main(int ac, char **av) { struct xy *a = foo(); struct xy b = *a; show_xy(a); show_nl(); show_xy(&b); show_nl(); return 0; } $ $ gcc -o struct_ret_ptr struct_ret_ptr.c lib6.c $ ./struct_ret_ptr [123, 456] [123, 456] $ $ cat struct_ret_ptr.c lib6.c | ./es_g.py - Err not found sdef@xy $ おおー! っと冷静に考えると # include "lib6.h" はコメント行なので struct xy { int x, y; }; の定義は lib6.c の中。 cat で lib6.c を先にもってきておかねばなりませんでした。 $ cat lib6.c struct_ret_ptr.c | ./es_g.py - [123, 456] [123, 456] $ ああ、良かった。 ということでテスト用のスクリプトもひっくり返す細工を入れます。 $ diff -u -L5 -L6 test5.sh test6.sh --- 5 +++ 6 @@ -6,13 +6,17 @@ NM=$1 LIB=$2 PRM=$3 + REV=$4 echo $NM $PRM if [ "$LIB" = "-" ]; then LIB="" fi + if [ "$PRM" = "-" ]; then + PRM="" + fi - if [ "$LIB" = "" ]; then + if [ x"$LIB" = x ]; then gcc -o $NM $NM.c -lm ./$NM $PRM ; echo $? > $NM.txt else @@ -21,10 +25,12 @@ fi rm $NM - if [ "$LIB" = "" ]; then + if [ x"$LIB" = x ]; then cat $NM.c | ./$ES - $PRM ; echo $? > $NM.es.txt - else + elif [ x"$REV" = x ]; then cat $NM.c $LIB.c | ./$ES - $PRM > $NM.es.txt + else + cat $LIB.c $NM.c | ./$ES - $PRM > $NM.es.txt fi diff -u -L gcc -L $ES $NM.txt $NM.es.txt || { echo NG ; exit 1; } @@ -75,6 +81,8 @@ chk unsigned_ptr lib5 chk struct_ret lib5 chk struct_ret lib5 "-x 123 -y 456" +chk struct_ret_ptr lib6 - rev echo OK $ $ ./test6.sh es_g.py uhyo if if a if a b if a b c sum a3 a4 a10 for2 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 ck1:55 ck2:55 ck3:55 ck4:55 ck5:55 ck6:55 ck7:55 global global g sin2 sin3 san san a san a b san a b c san a b c d arr arr2 struct struct2 struct3 arr_memb struct_arr struct_arg struct_arg 100 struct_arg 100 200 struct_arg 100 200 300 struct_arg 100 200 300 400 struct_arg 100 200 300 400 500 struct_arg_ptr struct_arg_ptr 100 struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 unsigned_ptr struct_ret struct_ret -x 123 -y 456 struct_ret_ptr - OK $
以前の int型 で導入した try_int() という関数がありました。
ポインタを無視して値として扱う悪行を、この try_int() が許してくれませんでした。
例えば $ ./es_g.py - <<EOL > void foo(int *a){} > int main(void){ int b[2]; foo(b); return 0; } > EOL Traceback (most recent call last): File "./es_g.py", line 888, inrcode = do_fcall( 'main', args, info ) File "./es_g.py", line 815, in do_fcall do_blk(name, env, body, info) File "./es_g.py", line 778, in do_blk do_expr(expr, info) File "./es_g.py", line 760, in do_expr return do_fcall(v, args, info) File "./es_g.py", line 812, in do_fcall args = [ try_int(t, v) for (t, v) in zip(types, args) ] File "./es_g.py", line 691, in try_int return int(v) if typ == [ 'int' ] and v is not None else v TypeError: int() argument must be a string or a number, not 'list' $ int b[2] の b の値は python の list [ None, None ] です。 foo() の引数に積む段階で引数の型は int * のハズが、 ポインタ無視の悪行で int とみなされ... try_int() が値が float だとややこしいので int() で整数に変換しようとして、 このエラーに至ってます。 try_int() の使用箇所は、関数呼出しの引数を積む箇所、 関数呼出しで値が返る箇所、変数宣言の初期化の箇所です。 $ ./es_g.py - <<EOL > int a[2]; > int *foo(void){ return a; } > int main(int ac, char **av){ int *b = foo(); return 0; } > EOL Traceback (most recent call last): File "./es_g.py", line 888, in rcode = do_fcall( 'main', args, info ) File "./es_g.py", line 815, in do_fcall do_blk(name, env, body, info) File "./es_g.py", line 778, in do_blk do_expr(expr, info) File "./es_g.py", line 762, in do_expr return do_type(expr, info) File "./es_g.py", line 719, in do_type val = do_type_val(typ, arr_n, val, info) File "./es_g.py", line 703, in do_type_val return try_int( typ, do_expr(val, info) ) File "./es_g.py", line 760, in do_expr return do_fcall(v, args, info) File "./es_g.py", line 820, in do_fcall return try_int( ret_type[1], ret ) File "./es_g.py", line 691, in try_int return int(v) if typ == [ 'int' ] and v is not None else v TypeError: int() argument must be a string or a number, not 'list' $ 同様のことが、int * の値を返す関数の場合も起こります。 $ ./es_g.py - <<EOL > int main(void){ int a[2]; int *b = a; return 0; } > EOL Traceback (most recent call last): File "./es_g.py", line 888, in rcode = do_fcall( 'main', args, info ) File "./es_g.py", line 815, in do_fcall do_blk(name, env, body, info) File "./es_g.py", line 778, in do_blk do_expr(expr, info) File "./es_g.py", line 762, in do_expr return do_type(expr, info) File "./es_g.py", line 719, in do_type val = do_type_val(typ, arr_n, val, info) File "./es_g.py", line 703, in do_type_val return try_int( typ, do_expr(val, info) ) File "./es_g.py", line 691, in try_int return int(v) if typ == [ 'int' ] and v is not None else v TypeError: int() argument must be a string or a number, not 'list' $ 変数宣言箇所もしかり。 そもそもこれまで、関数の返り値、引数、変数宣言について、 ポインタの形式とその扱いは全て異ります。 例えば $ ./es_g.py - <<EOL > int *foo(int *a){ int *b = foo(b); } > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [int] - [op, '*'] - [name, a] - - type - [int] - '*' - - br_s - '{' - - - type - [int] - - op - '=' - - op - '*' - [name, b] - - fcall - foo - - br_s - ( - - [name, b] - [etc, ;] $ 形式を見てみたいだけなので、実行できないようなコードではありますが... 引数の宣言では [ type, [ int ] ], [ op, * ], ... [ op, * ] は無視されて引数は int 同様の扱いです。 返り値では [ type, [ int ], * ] '*' は [ type ] の中に入ってますが、try_int() には [ int ] だけが渡されるので、 try_int() の中では int 同様の扱いになります。 変数宣言では [ type, [ int ], [ op, =, [ op, *, [ name b ] ], ... do_type_val() から try_int() が使われてますが、 do_type_val() の呼び元 do_type() では def do_type(expr, info): typ = expr[1] for e in flat_comma( expr[2] ): arr_n = False val = None if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] while e[:2] == [ 'op', '*' ]: e = e[2] # !!! if e[:2] == [ 'op', '[' ]: arr_n = e[3] e = e[2] name = e[1] val = do_type_val(typ, arr_n, val, info) new_val(name, val, info) return None '# !!!' コメントの箇所でポインタの * を無視する悪行で、 typ としては [ int ] だけが do_type_val() へと伝わってます。 この3か所で導入した try_int()。 値が float の時は上手く作用するのですが、 型が int のポインタの時は、返って邪魔になってます。
さてさて、どうしたものか...。
まずエラーが発生する底の try_int() def try_int(typ, v): return int(v) if typ == [ 'int' ] and v is not None else v これ自体に罪はありません。 ポインタなのに typ に [ int ] を渡す側が悪いに決まってます。 では、関数の引数の場合は do_fcall() のここ : types = [ e[1] for e in args_lst if e[0] == 'type' ] names = [ e[1] for e in args_lst if e[0] == 'name' ] args = [ try_int(t, v) for (t, v) in zip(types, args) ] env = dict( zip(names, args) ) do_blk(name, env, body, info) : 引数の並びから [ type, xxx ] を拾ってきて、 zip() で( 型, 値 ) の組にしてループで回し、try_int() に。 この時 int * でも「型」は [ int ] が渡され、リストに含まれている [ op, * ] は拾い上げられる事なく放置されます。 ここは以前 op_tree() を通すと、',' 演算子のツリーになって、ややこしそうなので、 それを避けてフラットなリストのままにしてました。 ですが、今となっては flat_comma() 関数もあるので、 * 演算子は変数名側のツリーになって欲しいかもです。 ちょっと変更して様子を見てみましょう。 elif k == 'fdef': e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] e[2] が引数の箇所で 例えば - - fdef - foo - - br_s - ( - - - type - [int] - [op, '*'] - [name, a] なので e[2][2] が [ [ type, int ], [ op, * ], [ name a ] ] になります。
$ cat es_h.py-diff.txt --- es_h.py- 2016-10-05 00:00:00.000000000 +0900 +++ es_h.py 2016-10-06 00:00:00.000000000 +0900 @@ -181,7 +181,8 @@ def tree_op(lst, ops): elif k == 'fcall': e = [ k, v, tree_op( [ e[2] ], ops )[0] ] elif k == 'fdef': - e = [ k, v, e[2], e[3], tree_op( [ e[4] ], ops )[0] ] + e2 = e[2][:2] + [ tree_op( e[2][2], ops ) ] + e = [ k, v, e2, e[3], tree_op( [ e[4] ], ops )[0] ] if k == 'op': return tstk_pop_ret( tree_op1(lst, ops, k, v) ) $ 引数の箇所にも tree_op() を適用してみます。 $ cp es_g.py es_h.py $ cat es_h.py-diff.txt | patch $ ./es_h.py - <<EOL > int *foo(int *a){ int *b = foo(b); } > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [int] - - op - '*' - [name, a] - - type - [int] - '*' - - br_s - '{' - - - type - [int] - - op - '=' - - op - '*' - [name, b] - - fcall - foo - - br_s - ( - - [name, b] - [etc, ;] $ 変更前 - - br_s - ( - - - type - [int] - [op, '*'] - [name, a] 変更後 - - br_s - ( - - - type - [int] - - op - '*' - [name, a] になりました。 [ type, [ int ] ], [ op, * ], [ name, a ] から [ type, [ int ] ], [ op ,*, [ name, a ] ] 複数引数の場合は 変更前 $ ./es_g.py - <<EOL > void foo(int *a, int b){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [int] - [op, '*'] - [name, a] - [op, ','] - - type - [int] - [name, b] - - type - [void] - - br_s - '{' - [] $ [ type, [ int ] ], [ op, * ], [ name, a ], [ op, ',' ], [ type, [ int ] ], [ name, b ] 変更後 $ ./es_h.py - <<EOL > void foo(int *a, int b){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [int] - - op - ',' - - op - '*' - [name, a] - - type - [int] - [name, b] - - type - [void] - - br_s - '{' - [] $ [ type, int ], [ op, ',', [ op, *, [ name, a ] ], [ type, int ] ], [ name, b ] 惜しい... ',' 演算子は [ type ] と [ name ] の結び付きを理解していないので、 最後の int b が分離されてます。 この引数の箇所はコンマ演算子の処理に頼れないようです。 自力でコンマごとに分離して、個別に tree_op() に渡す必要があるようです。
$ cat es_i.py-diff.txt --- es_i.py- 2016-10-06 00:00:00.000000000 +0900 +++ es_i.py 2016-10-06 01:00:00.000000000 +0900 @@ -169,6 +169,12 @@ def tree_op2(lst, ops, e, k1, v1): err_not_found("right term op='{}'".format(v1)) return [ op2( ops, e, k1, v1, r[0] ) ] + r[1:] +def tree_op_fdef_arg(lst, ops): + if [ 'op', ',' ] not in lst: + return tree_op(lst, ops) + i = lst.index( [ 'op', ',' ] ) + return tree_op( lst[:i], ops ) + [ lst[i] ] + tree_op_fdef_arg( lst[i+1:], ops ) + def tree_op(lst, ops): tstk_push( [ 'op', lst ] ) if len(lst) == 0: @@ -181,7 +187,7 @@ def tree_op(lst, ops): elif k == 'fcall': e = [ k, v, tree_op( [ e[2] ], ops )[0] ] elif k == 'fdef': - e2 = e[2][:2] + [ tree_op( e[2][2], ops ) ] + e2 = e[2][:2] + [ tree_op_fdef_arg( e[2][2], ops ) ] e = [ k, v, e2, e[3], tree_op( [ e[4] ], ops )[0] ] if k == 'op': $ $ cp es_h.py es_i.py $ cat es_i.py-diff.txt | patch $ ./es_i.py - <<EOL > void foo(int *a, int b){} > EOL --- lst --- - - fdef - foo - - br_s - ( - - - type - [int] - - op - '*' - [name, a] - [op, ','] - - type - [int] - [name, b] - - type - [void] - - br_s - '{' - [] $ やれやれだぜ。 こうした上で do_fcall() の (args_info, ret_type, body) = fdef[2:] (_, _, args_lst) = args_info types = [ e[1] for e in args_lst if e[0] == 'type' ] names = [ e[1] for e in args_lst if e[0] == 'name' ] args = [ try_int(t, v) for (t, v) in zip(types, args) ] env = dict( zip(names, args) ) ここに立ち返ります。 [ type, ... ], に続く形式が [ op, *, [ op, *, ... , [ name, xxx ] ... ] ] かも知れず [ name, xxx ] かも知れません。 一旦 [ *, *, ..., xxx ] あるいは [ xxx ] に変換して、もしあるなら [ *, *, ... ] の部分を typ 側に追加してみます。
@@ -697,6 +697,13 @@ def do_type_arr(typ, arr_n, val, info): def try_int(typ, v): return int(v) if typ == [ 'int' ] and v is not None else v +def flat_name(a): + if a[:1] == [ 'name' ]: + return a[1:] + if a[:2] == [ 'op', '*' ]: + return [ '*' ] + flat_name( a[2] ) + err('unexpected {}'.format(a)) + def do_type_val(typ, arr_n, val, info): if arr_n is not False: return do_type_arr(typ, arr_n, val, info) @@ -814,8 +821,14 @@ def do_fcall(name, args, info): (args_info, ret_type, body) = fdef[2:] (_, _, args_lst) = args_info - types = [ e[1] for e in args_lst if e[0] == 'type' ] - names = [ e[1] for e in args_lst if e[0] == 'name' ] + + lst = zip( args_lst[::3], args_lst[1::3] ) + # [::3] is [ type, [ ... ] ] + # [1::3] is [ op, *, ... ] or [ name, xxx ] + mv_ast = lambda t, a: ( t + a[:-1], a[-1] ) + lst = [ mv_ast( t[1], flat_name(a) ) for (t, a) in lst ] + (types, names) = zip(*lst) if len(lst) > 0 else ([], []) + args = [ try_int(t, v) for (t, v) in zip(types, args) ] env = dict( zip(names, args) ) では。 $ cp es_i.py es_j.py $ cat es_j.py-diff.txt | patch $ ./es_j.py - <<EOL > void foo(int *a){} > int main(void){ int b[2]; foo(b); return 0; } > EOL $ ふむ。 引数に渡す箇所は許された様子です。
では続いて、返り値の箇所
@@ -837,7 +837,7 @@ def do_fcall(name, args, info): was_flags(info, 'break') was_flags(info, 'continue') ret = info.pop('ret', None) - return try_int( ret_type[1], ret ) + return try_int( ret_type[1] + ret_type[2:], ret ) def do_global(lst, verb): genv = {} 例えば返り値の型は [ type, [int], * ] となってるはずなので [ type, [int *] ] を作って try_int() に渡すように変更しておきます。
ではちょこっと確認。
$ cp es_j.py es_k.py $ cat es_k.py-diff.txt | patch 変更前 $ ./es_j.py - <<EOL > int a[2]; > int *foo(void){ return a; } > int main(int ac, char **av){ int *b; b = foo(); return 0; } > EOL Traceback (most recent call last): File "./es_j.py", line 908, inrcode = do_fcall( 'main', args, info ) File "./es_j.py", line 835, in do_fcall do_blk(name, env, body, info) File "./es_j.py", line 792, in do_blk do_expr(expr, info) File "./es_j.py", line 769, in do_expr return do_op( v, expr[2:], info ) File "./es_j.py", line 591, in do_op return do_op2(v, args[0], args[1], info) File "./es_j.py", line 509, in do_op2 b = do_expr(b, info) File "./es_j.py", line 774, in do_expr return do_fcall(v, args, info) File "./es_j.py", line 840, in do_fcall return try_int( ret_type[1], ret ) File "./es_j.py", line 698, in try_int return int(v) if typ == [ 'int' ] and v is not None else v TypeError: int() argument must be a string or a number, not 'list' $ 変更後 $ ./es_k.py - <<EOL > int a[2]; > int *foo(void){ return a; } > int main(int ac, char **av){ int *b; b = foo(); return 0; } > EOL $
最後は変数宣言のところです。
@@ -724,13 +724,15 @@ def do_type(expr, info): if e[:2] == [ 'op', '=' ]: val = e[3] e = e[2] + cnt = 0 while e[:2] == [ 'op', '*' ]: - e = e[2] # !!! + cnt += 1 + e = e[2] if e[:2] == [ 'op', '[' ]: arr_n = e[3] e = e[2] name = e[1] - val = do_type_val(typ, arr_n, val, info) + val = do_type_val(typ + ['*'] * cnt, arr_n, val, info) new_val(name, val, info) return None 別関数にのれん分けして flat_name() と統合すべきかも知れないですが、 とりあえず従来の処理への追加でなんとかしてみました。
それでは確認です。
$ cp es_k.py es_l.py $ cat es_l.py-diff.txt | patch 変更前 $ /es_k.py - <<EOL > int main(void){ int a[2]; int *b = a; return 0; } > EOL Traceback (most recent call last): File "./es_k.py", line 908, inrcode = do_fcall( 'main', args, info ) File "./es_k.py", line 835, in do_fcall do_blk(name, env, body, info) File "./es_k.py", line 792, in do_blk do_expr(expr, info) File "./es_k.py", line 776, in do_expr return do_type(expr, info) File "./es_k.py", line 733, in do_type val = do_type_val(typ, arr_n, val, info) File "./es_k.py", line 717, in do_type_val return try_int( typ, do_expr(val, info) ) File "./es_k.py", line 698, in try_int return int(v) if typ == [ 'int' ] and v is not None else v TypeError: int() argument must be a string or a number, not 'list' $ 変更後 $ /es_l.py - <<EOL > int main(void){ int a[2]; int *b = a; return 0; } > EOL $
めでたしと言うことで、後出しのテストプログラムをでっちあげましょう。
$ cat int_ptr.c #include "lib6.h" int foo(int *a) { return a[0]; } int *bar(int *b) { return b; } int main(int ac, char **av) { int c[2] = { 1, 2 }; int d = foo(c); int *e = bar(c); show_int(d); show_nl(); show_int(e[0]); show_str(","); show_int(e[1]); show_nl(); return 0; } $ $ diff -u -L 6 -L 7 test6.sh test7.sh --- 6 +++ 7 @@ -82,6 +82,7 @@ chk struct_ret lib5 chk struct_ret lib5 "-x 123 -y 456" chk struct_ret_ptr lib6 - rev +chk int_ptr lib6 echo OK $ $ gcc -o int_ptr int_ptr.c lib6.c $ ./int_ptr 1 1,2 $ $ cat int_ptr.c lib6.c | ./es_l.py - 1 1,2 $ $ ./test7.sh es_l.py | tail struct_arg_ptr 100 200 struct_arg_ptr 100 200 300 struct_arg_ptr 100 200 300 400 struct_arg_ptr 100 200 300 400 500 unsigned_ptr struct_ret struct_ret -x 123 -y 456 struct_ret_ptr - int_ptr OK $
工事中...