プログラミング以外の話題はロビーのスレッドでお願いします。
ツールのソースについての質問などはこちらでも構いません。
CryptoStream.Write throws IndexOutOfRangeException
CanTransformMultipleBlocksがtrueのICryptoTransformを使ってCryptoStream.WriteするとIndexOutOfRangeExceptionがスローされる。
ICryptoTransform.TransformBlockに渡すバッファのサイズが少なすぎるようなので、それを直す。
CryptoStream.Read throws IndexOutOfRangeException
OutputBlockSizeが1、InputBlockSizeが3以上のICryptoTransformを使ってCryptoStream.ReadするとIndexOutOfRangeExceptionがスローされる。
内部バッファの状態をクリアするタイミングが誤っているようなので、それを直す。
Mono: DateTimeOffset.ParseExact
書式文字列にzzzを指定し、かつ入力文字列のオフセット部分にDateTimeFormatInfo.TimeSeparatorが含まれない場合、FormatExceptionがスローされる。 ("+09:00"はOK, "+0900"はNG)
パッチ
DateTimeFormatInfo.TimeSeparatorが含まれていない場合でも不正としないように直す。
メモ
Mono: XmlConvert.ToDateTimeOffset
フォーマットを指定しないでXmlConvert.ToDateTimeOffsetを呼んだ場合、タイムゾーンの部分が読み落とされる。
XmlConvert.ToDateTimeOffsetからDateTimeOffset.ParseExactを呼ぶときに渡されるformatsの順序が
- "yyyy-MM-ddTHH:mm:ss" など、Zもzzzも付かない書式
- "yyyy-MM-ddTHH:mm:sszzz" など、zzzが付く書式
- "yyyy-MM-ddTHH:mm:ssZ" など、Zが付く書式
になっているので、1でパース出来てしまうとタイムゾーンの部分が無視される。 なので、これを
- "yyyy-MM-ddTHH:mm:sszzz"
- "yyyy-MM-ddTHH:mm:ssZ"
- "yyyy-MM-ddTHH:mm:ss"
の順にすれば、タイムゾーンの部分を読み落とさなくなる。
Mono: XmlConvert.ToDateTimeOffset
フォーマットを指定しないでXmlConvert.ToDateTimeOffsetを呼んだ場合、タイムゾーンの部分が読み落とされる。
XmlConvert.ToDateTimeOffsetからDateTimeOffset.ParseExactを呼ぶときに渡されるformatsの順序が
- "yyyy-MM-ddTHH:mm:ss" など、Zもzzzも付かない書式
- "yyyy-MM-ddTHH:mm:sszzz" など、zzzが付く書式
- "yyyy-MM-ddTHH:mm:ssZ" など、Zが付く書式
になっているので、1でパース出来てしまうとタイムゾーンの部分が無視される。 なので、これを
- "yyyy-MM-ddTHH:mm:sszzz"
- "yyyy-MM-ddTHH:mm:ssZ"
- "yyyy-MM-ddTHH:mm:ss"
の順にすれば、タイムゾーンの部分を読み落とさなくなる。
Mono: WebClient.UploadStringAsync
WebClient.UploadStringAsyncが常にNotSupportedExceptionをスローする。
WebClient.UploadStringAsync()でSetBusy()が呼ばれたあと、UploadStringAsync()から呼ばれるUploadData()でもう一度SetBusy()が呼ばれている。
SetBusy()ではなく、CheckBusy()を呼べばOK。
gmcs: CS3019
trunk r148762のgmcsで発生。 以下のようなメソッドで警告CS3019が発生する。
- アクセス修飾子が指定されていないpartial class内で定義されている(クラス全体はpublic)
- CLSCompliant属性が付与されている
PartialClass.Method3がinternal classのメソッドとみなされているため'is not visible'となる? 試しにpublicを明示的に指定すると警告が出なくなる。
Mono: Enum.TryParse<TEnum>
TEnumがEnumでない場合でもArgumentExceptionをスローしない。
俺はラムダ式の書式に抵抗感を覚えるようで、
まだ
という匿名関数の形を良く使っている。
ただ、どうも打ち間違いが多いと思ったら、MonoDevelop 2.8でコードを書いていると、変数iが補完リストに出てこなかったのだ。
これと同じだね。(調査中の過程だったんだ、俺のコメント2は)
http://bugzilla.xamarin.com/show_bug.cgi?id=1132
で、ソースコードを追い始めた。
何かSystem.Console.WriteLineでコンソールに出力できなくって、
ファイルに書き出すコードを突っ込みまくって調べた。
どうやら、ファイルを変更するたび、ソースコードの構文木を作り、
Visitorパターンで最上位から流れるように追っかけるようだ。
ICSharpCode.OldNRefactory.Visitors.LookupTableVisitorは恐らく、変数などのidentifierを集めてAddVariableで変数の型、変数名、宣言自体の位置、変数使用可能範囲等を登録する。
たとえば、変数宣言でいうと、
最初は変数の型、次が変数名、次が、変数の宣言開始位置、次が変数の使用終了位置(おそらくenclosing blockの終わり)、次が修飾子で、
変数使用可能な範囲の始まり(補完リストに追加する地点:inListPosition)が、「セミコロンの後から」
これがラムダ式なら、
変数使用可能な範囲の始まりは、そのラムダ式の始まり、というわけだ。
これを踏まえて匿名関数のほうを見ると、
変数使用可能な範囲が
その「匿名関数の終わり」から「匿名関数の終わり」になっている。これは誤りだ。だから以下のように修正する。
===
CSharpBindingプロジェクトのMonoDevelop.CSharp.Completion.CSharpTextEditorCompletion.HandleCodeCompletion
から615行目default:へ流れて、636行目CreateCtrlSpaceCompletionDataの呼び出しがあり、
1961行目CreateCtrlSpaceCompletionDataの初っ端でこのLookupTableVisitorをメンバに持つ、NRefactoryResolverの設定を行う。
ここで、VisitCompilationUnitが行われている。同時にカーソルの位置も記録。
expressionResult.ExpressionContext == ExpressionContext.Defaultを満たして呼ばれるresolver.AddAccessibleCodeCompletionDataでは
カーソルの位置と、各変数の使用可能範囲であるかを確認して、その範囲内であれば、その変数を補完リストに追加する。
さっきの「匿名関数の終わり」から「匿名関数の終わり」の中にはカーソルが入らないので補完リストに加わらなかったところ、
加わるようになったので、正常に作動するようになる。
検証してある程度見当がついたら気が抜けてしまい、
後で見返したら自分がどうしてそういうコードだと理解できたのかわからずしばらく悩んだ。
多分、HandleCodeCompletionはCompletionTextEditorExtension.KeyPressから来たんだと思う。
その後、継承先であるCSharpTextEditorCompletion.HandleParameterCompletionが呼び出され、
その中で
NRefactoryParameterDataProviderのコンストラクタのオーバーロードである
public NRefactoryParameterDataProvider (TextEditorData editor, NRefactoryResolver resolver, IType type)
が呼ばれる。何故かコンストラクタを探すためのコードのはずなのにInvokeメソッドを探していたので、
等と実験的に変更してみるも、どうやらコンストラクタとなるメソッドが存在せず、実際にビルドして使ってみるとうまくいかなかった。
そこで、ITypeとなる引数を調べてみることにした。
このあたりで、MSDNには該当するInvokeメソッドなんか載ってないことを疑問に思った俺は検索を掛けたところ、
コンパイラが勝手に足す、という情報を得ることが出来た。
http://www.codeproject.com/KB/cs/delegatesneventsinternals.aspx
さて、IType typeはresolver.SearchTypeの戻り値であり、これは結局、ソースコードのパースで得られた型の集まりから、
該当する型を取ってくるものであるから、その生成過程がおかしいと考え、BeginInvokeやInvokeで検索し、その正体がDomTypeであることを突き止める。
https://github.com/mono/monodevelop/blob/master/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Dom/DomType.cs#L460
確かにInvokeしか追加されていない。
そこで
とか実験的に足してあげたところ、確かに、ビルドして試すと引数なしのコンストラクタの情報が掲示されるようになった。
==============-
自分が今のところわかっていないのは、
上記のコンストラクタにおいて、メソッド名を指定するような形が本来求められるわけだけれど、
一体メソッド名の型は何としたらいいのだろう?ということ。2引数のコンストラクタを定義したらうまくいくなどということもないだろうと思う。
あるいはコンストラクタとしてとらえるのをやめなければならないのだろうか?
ということである。
====
話題的に大丈夫かなあ、自分ではと思って書き込んでみたけれど、失礼だったり、邪魔だと感じる投稿であればそちらで消していただけると幸いです。
私も興味ありますし、こういった内容もいいかなと思っているので、書き込んで頂いて大丈夫です。
殴り書き
何度でも繰り返す。「俺は一体掲示板を何だと考えているのだ」と。
makefileを読んだりしていないけれど、Mono,GTK#,MonoDevelopのWindows版公式ビルドはどのようにビルドされているのだろう。
使っているのがMSのコンパイラ + MS.NET Frameworkである気がしてる。
==============【以下しばらく読まなくてもいい】================
Mono上で4.0向けGTK#アプリを作ったところ(実際にやったのはMonoDevelopのslnのビルド)、問題が二点あった。
●バックアップとるとか、あるいはMonoとGTK#の再インストールとか覚悟してね。
●各ライブラリはgithubからzipファイルへのリンクをクリックしてmasterの最新版をダウンロードして展開して参照している。
いい加減バージョン管理ソフトだのビルドツールくらい、コマンドライン慣れろよ>俺
●なお、MonoDevelopはそのソース中、拡張メソッドを使っているので.NET 2.0向けのビルドは不可。
3.5は知らんが、確か参照しているプロジェクトが何か.NET 4.0用のライブラリ・クラスを使っていた気がするのでたぶん無理じゃないかなー。
一つ目。
Mono側に入っているlibgtk-win32-2.0-0.dllのバージョンは2.16.6で、Windows版GTK#についてくるのは2.24とかその辺のバージョンなので、
Mono側から持ってきたファイルで上書きします。(なぜかそうしないとコケる。)
二つ目。
Windows版 GTK#インストーラでは、MS .NET 2.0向けにMono.Cairo.dll,Mono.Posix.dllがインストールされる。
MonoのインストーラではMonoのgacに2.0用と4.0用のMono.Cairo.dll,Mono.Posix.dllがインストールされる。
MonoDevelopをMonoでv4.0用にビルドすると、gacから.NET 4.0のものが引かれる。
できたアセンブリを、Windows上の.NET上で動かそうとすると4.0用のライブラリがないのでコケる。
まぁしょうがないから、Monoからバイナリをコピーしてmonodevelop.exeのディレクトリに入れて…一応動くはず。
●なんかメモによると、internalとかprivateになっているやつをpublicにしないと、MonoDevelopで定義場所探したら何か他のプロジェクトにある同名クラス(っていうか同じ仕組みで生成されるファイル)が参照されていて謎とかあった。当時(2012-06-24)のことあまり思い出せなくて、それでコケたかは忘れた。
●https://github.com/mono/monodevelop/blob/master/main/src/addins/MonoDevelop.GtkCore/libsteticui/CodeGenerator.cs#L34
●…とまぁ落とし穴が沢山あるので、
http://atsushieno.hatenablog.com/entry/2012/08/27/130305
を知った時、俺はこっそり色々心配してたとか。(「余計なお世話」なの自覚しているので「こっそりと」)
●Linuxサイドからクロスプラットフォームの世界を見せるなら、v4.0向けの書き方やライブラリ禁止ってことだね。
●まぁ、Monoでのビルドをデフォルトにしている偏屈な俺と違って、
普通なら、WindowsではMS .NET Frameworkを使うだろうし、作った環境で動かすだけならトラブルにはならないだろうから、
過剰な考えなんだろうなーと。
●「多分.NET 2.0より後でGTK#がビルドできないのはMono.PosixやMono.Cairoが提供できないから」って点も
●「MSのコンパイラを使っている」説の傍証の一つになっている。俺の中では。
==================【ここから本題】======================
と、ここまでが前座。気づいた時の経緯の一つで、実は殆ど本題には関係ない(笑)。
「公式ビルドはMSのコンパイラを使っている」と俺が考えている、ってことが大事。
あるとき(Mono 2.11.1,2012-04-23)から、Mono最新版にくっついてくるvbncで、
libpathとか指定なしで、vbのコードをコンパイルしようとするとMicrosoft.VisualBasic.dllが無い!って言ってくるようになったんだ。
●しばらく調査を面倒臭がっていた。
C:\Program Files\Mono\v2.11.4\bin
C:\Program Files\Mono\v2.11.4\lib
C:\Program Files\Mono\v2.11.4
の3つをPATHに放り込んでるから、vbnc.batで実験してた可能性大だが、MonoDevelopなしでも再現する。
また、
vbnc.batの中身を
@"C:\PROGRA~1\Mono\V211~1.4\bin\mono.exe" %MONO_OPTIONS% "C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.5\vbnc.exe" %*
から、
@"C:\PROGRA~1\Mono\V211~1.4\bin\mono.exe" %MONO_OPTIONS% "C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\vbnc.exe" %*
とか書き換えてもまだ「ない」とか言ってきた。
vbncのコードは、一部ライブラリを除きVB.NETで書かれているので、MSのvbcを使って.NET 4.0向けにビルドとかやってみたけど無駄だった。
●MfAもPSSuiteも使ったことないけど。
上記のように色々弄った状態ではあるが、
https://github.com/mono/mono-basic/blob/master/vbnc/vbnc/source/General/Compiler.vb#L119
あたりがその辺の情報を持ってそうと考え、
https://github.com/mono/mono-basic/blob/master/vbnc/vbnc/source/General/Compiler.vb#L467
LibPathを参照してそうなところがここしかなく、ここを見る
https://github.com/mono/mono-basic/blob/master/vbnc/vbnc/source/General/Compiler.vb#L989
こう大雑把に書いてログ取り。
●try catch finallyとかusingどこ行った>俺
出力結果:
pass
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\System.Core.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\System.Xml.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\System.Configuration.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\I18N.CJK.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\I18N.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\System.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\Mono.Cecil.VB.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\Microsoft.VisualBasic.dll
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.0\vbnc.exe
C:\PROGRA~1\Mono\V211~1.4\lib\mono\4.5\mscorlib.dll
何で最後だけ4.5なんだよ(怒)
「AppDomainのソースコードから呼び出されるCのコードはバージョン非依存になってそうな気がする」と勘を働かせ、Cに慣れてないこともありそこから逃避。
ただ、納得がいかず、うんうん唸りながら検索を掛けて一つの仮説を思いつくことになる
MS .NET 4.5って、
http://www.west-wind.com/weblog/posts/2012/Mar/13/NET-45-is-an-inplace-replacement-for-NET-40
MS .NET 4.0を置き換えちゃうんだな。
http://atsushieno.hatenablog.com/entry/2012/06/14/141356
の件があって、RTMを待って入れている俺の環境でもそうなっている。
(8月21日でも、当時結構RCとか紛らわしい状態だった。以上のことは俺がどこも間違えてない前提。)
MSのコンパイラvbcは、ビルドしたアセンブリを4.5扱いにするんじゃね?
で、公式のビルドマシンもそうなっているから、今回のトラブルが発生しているんじゃないか?
ってのが自分なりの今の仮説。
…まだやってないけど、.NET 4に戻せば治っちゃったりして。
バージョンを下げるのって負けた気がするからあまりやりたくないのだが…(いつ検証するか不明。しないかも)
ふと思ったこと。
●エンドユーザーへの説明・影響
●テスト体制・リリースマネジメントの改善
=======================
どうでもいいが個人的にWikiの記法って嫌いだ。コメント扱いのつもりでアスタリスクやナンバーサインを書くとorz
何か重要なこと言ってない気がする。
補足というか本題冒頭への追加というか:
Microsoft.VisualBasic.dllをMonoのgacの4.0のディレクトリから4.5用のディレクトリ(こっちにはこれがない)にコピーすればコンパイルできることから4.0ではなく4.5のディレクトリを読みに行き、同ディレクトリ内のMicrosoft.VisualBasic.dllを探しているものと考えた。そこで、読み込むディレクトリを決定している部分はどこかという発想に至った。
>>25
vbncは確か4.5が含まれるようになる前から既にMicrosoft.VisualBasic.dllが無いって言うようになってた気がしますね。
どうでもいいが個人的にWikiの記法って嫌いだ。
すみません、使い慣れてる書式なのでこの掲示板でも使ってる次第です。
書式無しで書き込めたほうがいいですか?
そのうちにでも書式選べるようにしてみます。
どうやら俺はまた誤診をしたようだ。
上記は単に、ファイルを壊した結果アセンブリとして認識されず、当然アドインとして認識されない結果としてエラーが表示されなくなっただけだった。
「エラーメッセージが間違っている」までは正しいが、
SharpSvn.dllはVC9のCRTを参照しているため、VS2008 Redist SP1が正しい。
>>30
BinaryReader.ReadBytesメソッドの動作についてですが、ご指摘頂いた通り
記述されている内容が誤りがありました。
正しくは「ReadBytesメソッドではEndOfStreamExceptionがスローされることはなく、
実際に読み込めた分の長さのバイト配列が返される」となります。
従って、ReadBytesを呼び出す場合はEndOfStreamExceptionがCatchされることはありません。
ファイル末尾に達したかどうかの判断は、ReadBytesが返す配列の長さが
引数で指定したバイト数よりも少ないかどうかで調べることができます。
当該ページの記載内容も修正いたしました。 ご指摘ありがとうございました。
ご報告ありがとうございます。
検証したところご指摘のC#コードには文法上の問題はないことを確認しております。
>>35
例えばgit cloneで/home/user/mono/にクローンした場合なら、/home/user/mono/mcs/class/lib/monolite/になります。
mcs/class/lib/のディレクトリはソースツリーの中に存在しているはずなので、そこにmonoliteディレクトリを作成して、
バイナリをコピーしてくればよいことになります。
ただ注意事項としても書いているように現在のバージョンでも機能するかはわかりませんし、System.Core.dllなど
ほかに必要になるアセンブリもあるかもしれません。
>>37
例えば、インストールするディレクトリを自分で決めたいとか、コンパイルオプション等を指定してビルドしたいとか、
あるいはソースを修正しながらデバッグやテストをする必要があるとか、そういった目的がある場合は
自分でソースからビルドするという選択になると思います。
逆にhttp://www.mono-project.com/download/で紹介されているパッケージの場合はビルド済みのものを
インストールすることになるので、上記のようなことはできませんが、特にそういう目的に使う予定がなく、
とりあえず動作する環境が用意できればよいのであれば、パッケージからインストールするという選択になると思います。
>>37
追記です。
安定版はパッケージが用意されますが、開発中の最新の状態を使いたい場合はパッケージがない
ことがほとんどなので、そういった場合も最新のソースを取得してビルドすることになります。
>>41
32bitということは./autogen.shで'--host=i686-w64-mingw32'を指定したものかと思いますが、
だとするとビルド成果物のMonoランタイムはWin32 APIに依存するものになっているので、
Windows以外では動作しません。
Windows上でビルドしたものをMacOSやLinuxにインストールするのであれば、その環境向けに
クロスコンパイルする必要がありますが、実際にVisual StudioとMonoのソースでそういったビルドが
できるのかはわかりません。
1>>
「二分木を使った数式の逆ポーランド記法化と計算」のページを拝見しました。
難解なアルゴリズムがわかりやすく簡潔に解説されていてとても肝銘を受けました。すばらしいです!!
このページのC言語のソースコードを読んでいて、strncpyの使い方が気になったのでご報告します。具体的には次のような行です。
strncpy(node->left->exp, node->exp, pos_op);
strncpyはコピー先に行末コードを書き込みませんので、次のような記述を追加した方が良いかも知れません。
node->left->exp[pos_op] = '\0';
なおstrncpyはrightのnode用に使われており、計2箇所あります。
私の勘違いかも知れませんが、よろしくお願い致します。
>>43
問題のコードについて、strncpyが終端文字を書き込まない場合があることを把握していませんでした。
コードを修正し、終端文字で終端させるよう記述を変更しました。
なお、読みやすさを考慮してmemsetでバッファ全体をゼロクリアしてからstrncpyする方法を
採らせていただきました。
記事をお読みいただき、またご指摘いただきありがとうございました。
>>44
「二分木を使った数式の逆ポーランド記法化と計算」のコードを修正していただき、どうもありがとうございます。
二度手間で大変申し訳ありませんが、気になる箇所をもう1つ見つけてしまいました。
計算式から括弧( )を取り除くための関数remove_bracketの最後の部分です。自分自身を再帰呼びだしした直後にreturn 0で正常終了しています。具体的には次の3行です。
この書き方だと、最後から2行目のremove_bracket(exp)がエラーコード(-1)を返したとしても、必ず次の行のreturn 0が実行され、呼びだし元にはエラーの発生は伝わらないと思われます。
細かなこと恐縮ですが、ご確認いただければ幸です。
>>45
ご指摘のとおり、再帰的に呼び出したremove_bracketの結果が返されず常に0となるため、現在の実装には問題があります。
(本来return remove_bracket(exp);
とすべきところでreturn
の記述が抜けていました)
ただこの問題以前に、そもそも正しく開いていない/閉じていないカッコの扱い自体に不備が多いため、
それも含めて修正したいと思います。
修正次第あらためてお知らせしますが、もしカッコの処理(remove_bracket)以外で不備が見つかりましたら
遠慮無くご報告いただければと思います。
なお現在の実装での動作です。 いずれもエラーとなるよう修正したいと思います。
- 式
2+3)
→左項2
・演算子+
・右項3)
と解釈される
- 式
(2+3
→演算子なしの単項(2+3
と解釈される
- 式
1+(2+3))
→左項1
・演算子+
・右項(2+3))
と解釈される
- 式
1+((2+3)
→エラーとなる(意図した動作) unbalanced bracket: ((3+2)
「二分木を使った数式の逆ポーランド記法化と計算」のコードを修正していただき、どうも有り難う御座います。
今までとは趣きが大きく変わりましたので正直なところ驚きました。
本格的に書き直していただきましたので、今一度、細部に渡って確認させていただきました。
致命的な不具合は見つかりませんでしたが、気になった箇所が幾つかありましたので、それらを列挙させていただきます。
どうでも良い点ばかりかも知れませんが、多少なりとも何かのヒントにでも繋がれば幸です。
get_pos_operator関数(size_t型)の戻り値が-1になる場合がある。
処理系によってはNG?
size_tは通常unsigned型で定義されるので、ゼロ以下の値は避けた方が良い。
この関数中のpos_operator変数(size_t型)も同様。
この関数の呼びだし元も見直した方がよいかも知れません。
C言語の場合はchar型のポインタで書き換えた方がスッキリすると思います。
ポインタ型が使えない他言語とアルゴリズムを揃えておきたい場合は、size_tをint型やlong型で置き換えるとよいかも。
この関数内では演算子の最低の優先度をあらわす変数priority_currentを4で初期化していますが、
対応する演算子を増やすなどした場合、この値を変更するのを忘れると不具合の原因になりえます。
4よりも大きな、できるだけ大きな整数値で初期化した方が良いと思います。
以下のコードの中で★印で囲まれたコメントは、私が追記したモノです。
==========================================================================
size_t get_pos_operator(char *exp) // ★ size_t型の関数の戻り値に-1? ★
{
==========================================================================
calculate関数内で子ノードのポインタにNULLを代入している次の2行は不要?
==========================================================================
int calculate(Node* node)
==========================================================================
remove_outer_most_bracket関数の先頭部分が冗長で少々難解。次の2点で簡素化可能。
この関数は何度も呼び出されるので、できるだけ先頭部の処理を軽くしておきたい。
丸括弧の対応を厳密にチェックする関数を別途用意して、数式入力直後に1度だけチェックするようにすれば、この関数の動作を簡素化できる。
この関数の目的を「式全体が括弧で括られている場合の処理のみ」に限定した方がよい。
特にこの関数の先頭付近でのstrlenは止めた方がよい。
==========================================================================
int remove_outer_most_bracket(char *exp)
{
// ★ この関数は重いので、旧バージョンのように必要な場合のみ呼び出した方が良い ★
// ★ 例えば、次のように変更 ★
// ★ if ( exp[0] == '(' && exp[i-1] == ')' { ★
// ★ if ( remove_outer_most_bracket(exp) < 0 ) return -1; ★
// ★ } ★
// ★ return 0; ★
==========================================================================
parse_expression関数内で使われているmemsetは避けた方がよい?
計算式が長大にると遅くなる。
==========================================================================
int parse_expression(Node* node)
{
==========================================================================
3つのtraverse関数の出力結果が見難いので、各式の間に空白等の区切り文字を入れた方がよい。
区切り文字を入れないと、2桁以上の数値を計算した場合に問題あり。
==========================================================================
void traverse_postorder(Node* node)
{
}
==========================================================================
次はソースコードの書き方に関する問題なので、何らかの正解があるわけではありません。
私個人の意見としては "else if"が何度も続くコードは少々読みにくいと感じます。
次の場合のように、if文に連なるブロックが途中でreturnで抜けている場合は、
後続の条件判断は"else if"ではなく"if"文にした方がスッキリして読みやすいと思います。
==========================================================================
int parse_expression(Node* node)
{
==========================================================================
その他
任意演算のアルゴリズムは処理速度を要求されるインタープリタの一部として使用されるケースもあると思います。
多少は速度を意識した書き方にしておいた方が、多くの方から好感を持たれると思います。
それでは良いお年をお迎えください。
詳細にわたりレビューしていただきありがとうございます。
C言語は普段使いの言語ではないため細部が甘くなりがちで、またこういった意見を
いただく機会もなかなかないので、大変ありがたく思います。
指摘いただいた箇所については反映したい部分が多々ありますが、修正が完了するまでには
また時間をいただくことになりそうなので、勝手ながら留意事項として本文中に
書き込みへのリンクを貼らせていただく形での暫定的な対応とさせていただきました。
(http://smdn.jp/programming/tips/polish/#Implementation_C)
現状本文の方も変更を考えている部分があるので、それと合わせて修正後に
また改めてお知らせいたします。
(おそらく来年2018年1月中旬〜下旬ごろになると思います)
なお、以下は頂いた部分についての現時点での修正対応予定です。
概ね、コードの簡略化・読みやすさを優先とし、恐縮ながら速度の向上が得られる
のみとなる修正はしない、という方針に基づく判断となります。
- 採用の方向
- size_t型に-1? (char*よりはintへの変更で修正予定)
- priority_currentの初期値をINT_MAXに (なぜ今までそうしなかったのか…)
- traverse系関数の出力に空白を追加 (前回変更は出力を変えない変更のみとしたため、今後本文と合わせて修正予定)
- 採用に肯定的
- remove_outer_most_bracketの先頭部の簡素化 (現行実装が冗長な感があったので、頂いた案を精査の上、改善)
- remove_outer_most_bracketの再帰呼出し前の必要性チェック (上をそのまま採用するなら、これも同様に)
- returnで抜けるifの分離とelse部の展開 (読みやすさを見て決定)
- 採用に消極的
- parse_expressionでのmemset,strncpy (C言語レベルでもleftとrightで処理に相違がないようにしたい、C言語レベルでの差異を理由に意味的にも何か違いがあるのかという疑問が生じないようにしたい)
- calculate内で子ノードのポインタにNULL (処理上は冗長だが、子ノードを持たない状態にすることを明示しておきたい。 また、今後の拡張として計算過程のツリーを表示する場合を考えていたので、そのままのしておきたい。)
- コメント文 (サンプルコードの説明ではコードの意図と目的を明確にしたく、それに従い体現止めの採用には消極的、ただ文体の不一致修正はする)
実用ではなくあくまで理解のためのサンプルコードという位置づけのため、また扱う文字列長も
高々キロバイト単位のため、実用や高速化を目的とした改善はコードの利用者各自で
行ってもらいたく思っています。
速度を意識した改善は行いたくはありますが、あくまでサンプルコードとしての位置づけでの公開ですし、
また実用を考慮しだすと速度以外にも変更したい部分は多々あります。
例えば、演算子も*や/ではなく×や÷を使えるようにしたい、その他記号等も使用したいと考えると
char配列ではなく非ASCIIに対応する文字列型を使うべき。 あるいは計算では浮動小数点ではなく
固定小数点を使うべき、など。 これらに対応するとコード量が増えてサンプルとしては長大になるため、
ここで扱うべき範囲を超えるとして省略しています。
もちろん、memsetやstrlenに関する点は実用を主眼とした場合には有用ですので、今後コードを書く際の
参考にしたいと思います。
いずれにせよ、改善に繋がるご意見は大変ありがたいので、他にももし改良点がありましたら
気兼ねなく書き込んでいただければと思います。
あるいは、改善点を修正したコード全文をこちらに貼っていいただければ、本文に掲載する
コードに直接反映しないにしても、本文中にて紹介という形にさせていただくこともできます。
それでは良いお年を。
>>48
大変遅くなりましたが、頂いたご指摘をもとに修正した内容を先ほど公開いたしました。
変更した箇所としなかった箇所は下記のとおりとなります。
パフォーマンス関連での修正にはご満足いただけない部分があるかとは思いますが、ご確認いただければと思います。
変更した箇所
- size_t型に-1
- indexはintに、lengthのみsize_tを使用するように変更
- priority_currentの初期値をINT_MAXに
- INT_MAXを使用するように変更(他言語も同様に整数の最大に相当する定数値に置き換え)
- traverse系関数の出力に空白を追加
- すべての記法で項および演算子の間に空白を入れるように変更
前置記法・後置記法では末尾にも空白が出てしまうが、実装の簡略化のため割り切り
- remove_outer_most_bracketの先頭部の簡素化
- 括弧の対応の検証は、新たに関数validate_bracket_balanceを用意して分離し、parseする前に一度だけ呼び出すように変更
式中の文字の検証と同時にlenを計上することにより、strlenの呼び出しを削除
冒頭のif-else ifブロックは変更せず(以下理由)
以下のようにすればi=0からスキャンになり冒頭の見かけ上の冗長さはなくなるが、逆に開き括弧が出現するたびにif (0 == i)が評価されることになり、最終的な判断としては変更なしに
- remove_outer_most_bracketの再帰呼出し前の必要性チェック
- 括弧を削除したあと、さらに外側に括弧が残る場合のみ再起呼び出しするように変更
- returnで抜けるifの分離とelse部の展開
- if内でreturnする場合はifを単離し、else部は展開
parse_expressionではreturnする前に不要な処理を行わないよう評価順序も変更
変更しなかった箇所
- has_outer_most_bracketの値にtrue/false
- 関数の成功/失敗をintで統一している、またMSVCでのstdbool.hの扱いに難あり?
検証の上使用するにしても結局ここだけなので、現状維持
- parse_expressionのmemset
- 先にmemsetするべきと考える文化に従うならここ(あるいはcreate_node)でやるのが適当、それを冗長と考えるなら\0で終端するのが適当、そのどちらに寄せるかの判断はここでは留保とし、現状維持
(パフォーマンスを再優先にした改善や、より高度な実用を目指した変更は、やはり目的に応じて個々でやってもらいたいし、それができるようMITライセンスを設定している)
- parse_expressionのrightに対するstrncpyはstrcpyにできる
- leftとrightはC言語レベルでも差異が出ないようにしておきたい
三項以上の演算子や関数に対応する場合など(cond ? cond-true : cond-false
, func(arg1, arg2, arg3...)
)を考えると、二分木の場合に特化した最適化の感もある
- calculateでの子ノードへのポインタにNULL設定は省略できる
- 冗長といっても単純代入、再帰呼出しやstrlen等に比べればコストは軽微と考え、意味上の明確さを優先のため現状維持
変更箇所の差分を次の投稿にて別途掲載しますので合わせてご覧ください。 また、上記以外(コード以外)の変更点については下記の更新情報をご参照ください。
http://smdn.jp/programming/tips/polish/#DocumentLog
改めて、ご指摘をいただきありがとうございました。
はじめまして。
プログラムの参考に閲覧しました。
プログラムに疑問がわいたのでこちらに失礼します。
Programming / Tips / 二分木を使った数式の逆ポーランド記法化と計算
にある最後のC言語でのコードについて。
入力計算式を
1 (10 * 10) / 10
などとカッコの前後に演算子のない数値を記入したとき。
計算成功,になりますが,計算結果はでたらめです。
本来は計算成功ではないと思われますが,これは仕様なのでしょうか。
初歩的な質問で,失礼しました。
>>52
ご質問ありがとうございます。
制限事項としても明記していますが、例示いただいた式のような
「暗黙の乗算を含む部分式に関する動作は未定義」となります。
ですので、ご指摘のとおり本来なら計算成功ではありませんが、
現状の動作で仕様通りということになります。
現時点での実装では、こういった式をエラーとするような処理は
実装していないため、エラー報告はされずに処理が継続し、
最終的には計算成功扱いとなるという動作になっています。
また、式"2(1+2)"は"2*(1+2)"のように暗黙の乗算を含む式とは解釈されず、
計算できない単一の項"2(1+2)"として扱われそのまま出力されるため、
計算成功扱いにはなりますが期待するような結果は出力されないという動作になります。
>>54
ご指摘の通りキューの動作は「先入れ先出し」となります。
記載内容に誤りがあったので修正しました。
ご指摘いただきありがとうございました。
.NET 同様の解説法でWPFについてもお願いします。
>>56
WPFについては、解説を書けるほどの知識を現時点で持っておらず、また今後使うことがあるとしても.NET MAUIになると思っています。
ですのでリクエスト頂いたところ恐縮ですが、WPFについてはご期待にお応えすることができません。