過去、パーザーに凝ってたので、ICPCで出題されるパーザーの交付をきちんと解答するやり方を書いている。
コツを知っていれば誰でも迅速かつ正しく作成することができる。
方法
まず、方法を考えている。
基本的には、
・再帰下降型/ LL(1)文法の構文解析
・副作用を回避する
効率は無視する
こんな風に実装する。
最初は筆記パーザーの原則だ。
間違ってもLR構文解析を実装することができるなんてありえない。
1日では終わらないと思う。
そして、演算子の優先順位解析も提起しないことをお勧めする。
式にのみ適用することができる上にLLよりも若干アルゴリズムが厳しい。
第二は、バグなしのコードを一発で書き込みするには、非常に重要なことではないかと思う。
まともなCプログラマと副作用がある・なしを意識していること自体が少ないのだが。
最後は、細かいところを惜しみながら考えてコードを書くのが遅くなるという程度の意味だ。
LL構文解析
LL構文解析は、BNFで表現された文法を、トップダウン的に分析する方法で、 手でパーザーを書く場合、これを再帰的に関数呼び出しで実装される。
BNFだけ書けば後はそれをコードにするだけだ。
LL文法を実装する際に厄介なのは左再帰をどのようになるかということで、 それはLL(1)に該当しない文法をどう処理するかということだ。
左再帰
実際にはBNFから左再帰はかなりの頻度で出る。
これ右側再帰型に取る必要があるが、 構文解析の教科書には、なんだか難しいことが書いてある。
次のような構文があったかのように
A = A x | y
これが
A = y A '
A '= x A' | ε
このように右再帰に修正することだ。
また、一般的に議論を展開すると左再帰を右再帰 "形式”で書き換えられているが、これらの変換を手だることは面倒で、何よりも完成文法が完全に直感的ではない。
再帰下降型のパーザーの手によって記述するから、何も形式的に変換する必要がないこと、 個々の場合 に応じてくれて簡単な方法で書けば良い。
そして、その場合だが、実際にはほとんどの繰り返しもある。
A = A x | ε
で、単純に0回以上出現したり、
E = E + T | T
など左結合演算子を、記述、主な事例は、この両方のだ。
まず右再帰に変換してもいいが、 ここ再帰下降型パーザーCで作成しようとしているのだから、 BNFにとらわれる必要はあ らない。
中括弧で囲まれた部分を繰り返しと定義
A = {x}
E = T {+ T}
このような感じが良い。
繰り返しEBNFにあるが、そんなものの存在も考慮する必要はない。)
(1)LLに該当しない文法の場合
再帰的に作成する場合
一つの先読みでは、次の呼び出し関数を決定することができないとダメだ。
例えば、Cの文法を考えると、intという文字をインポートすると、 次のfoo;で来る場合は、変数の宣言であり、foo(){...} で来る場合は、関数の宣言だ。
すなわち、この段階では、次の文字を読み込んで呼び出される関数が決定されていないため、Cの文法は、LL(1)ではないということだ。
これは再帰下降型では解析ることはできない。
可能な解決方法では、先読みの文字を増加させる、バックトラックなどがある。
文字を増やす場合
たとえば、前の例であれば、int fooまでロードしても構文要素を決定することができないが、次の文字が(あれば、関数の宣言と見ることができる。
すなわち、決めることができないところについて
決定されるまでの記号を先読みするということだ。
しかし、この場合、入力を順次読みこみながら解析するなど、コードを記述することが困難になって(今回の書くパーザーはそんなものではない が...)
また、判定の部分が煩雑になる。
より強力な方法は、バックトラックだ。
これは候補を全部試みるとだけだが、 Cとコードを書くことがやや複雑で、速度にちょっと不安だ。
だけど、構文クラスでは、LL(∞)は、このBNFで記述することができる構文クラスに一致する(...と思う)。
色々書いてきたが、それはとにかく面倒だ。
しかし、自信を持ってしてください。
パーザー系でLL(1)に遅れはほとんどない。
おそらく再帰下降で書くのが普通だからだと思うのだが。
実装
一般的に、1文字ずつ入力をロードしながら分析することが主流のような感じだが、ここで、読み取りを完全に分析する前にする。
これにより、分析関数からの副作用を削除する。
副作用の多いコードより動作が把握しやすくなると思う。
これらの仮定に基づいて関数の形を考えている。
入力は文字列、出力は分析の結果、加えて、分析し残した文字列を返する。
pair <分析結果の型、char *> parse(char * p)
{
...
}
通常、これらの関数を書いて並べることになる。
返り値は
typedef pair <分析結果、char *> parsed;
などとしておくと(タイピング)簡単だ。
例
構文解析の頻出、四則演算を実装しようとする。
とりあえず、構文を定義する。
Expr = Expr + Term | Expr - Term | Term
Term = Term * Fact | Term / Fact | Fact
Fact =(Expr)| number
右再帰を含んでいるので、次のように作成する。
文法はかなり適当だが。
Expr = Term {(+ | - )Term}
Term = Fact {(* | /)Fact}
Fact =(Expr)| number
このコードに落とす。
typedef pair <int、const char *> parsed;
parsed expr(const char * p);
parsed term(const char * p);
parsed fact(const char * p);
parsed expr(const char * p)
{
parsed r = term(p)、 while(* r.second == '+' || * r.second == ' - '){
char op = * r.second;
int tmp = r.first;
r = term(r.second + 1);
if(op == '+')r.first = tmp + r.first;
else r.first = tmp-r.first;
}
return r;
}
parsed term(const char * p)
{
parsed r = fact(p);
while(* r.second == '*' || * r.second == '/'){
char op = * r.second;
int tmp = r.first;
r = fact(r.second + 1);
if(op == '*')r.first = tmp * r.first;
else r.first = tmp / r.first;
}
return r;
}
parsed fact(const char * p)
{
if(isdigit(* p)){
int t = *(p ++) - '0';
while(isdigit(* p))t = t * 10 + *(p ++) - '0';
return parsed(t、p);
}
else if(* p == '('){
parsed r = expr(p + 1);
if(* r.second!= ')')exit(0); // invalid input
return parsed(r.first、r.second + 1);
}
else
exit(0); // invalid input
}
いいから早く書いていことを目的にしているので、 字句解析と構文解析木の作成などは行わず、すべてパーズ中に実施している。
これに、入出力部を付ければ一応完成だ。
たとえば、1ラインごとに数式が含まれているような入力と
int main()
{
string str;
while(cin >> str)
cout << expr(str.c_str())。 first << endl;
return 0;
}
これでOKだ。
それと、上記のコード
char *にconst付け忘れ以外は間違いしていない。
エラー処理
空白のスキップなど
後は必要に応じて加え、 個々の問題について分析関数を適当に作成すれば大丈夫。
10分で作成できるか?
ラベル(コンテンツのようなもの)
html
(21)
MUGEN
(1)
SS
(1)
アニメ
(22)
アニメレビュー
(9)
アメリカ合衆国大統領
(3)
インターネット
(1)
カニバリズム
(1)
キリスト教の正体
(93)
クソゲー&バカゲー レビュー
(9)
ゲーム
(4)
ゲーム ドラゴンクエスト
(3)
ゲーム・周辺機器
(4)
けものフレンズ
(8)
その他
(2)
ドラえもん
(1)
ニュース
(1)
モバイル
(1)
よくある誤解
(5)
引っ越しの基礎知識
(1)
映画
(1)
音声圧縮技術の音質評価基準
(16)
学校社会の適応と、成人後の労働社会の適応の違い
(4)
艦これ
(8)
基礎からわかるホームページの配色
(2)
旧日本海軍
(3)
経済学
(141)
経済学(ポピュリズム)
(17)
芸能人
(2)
建築
(1)
研究批評を書く人のための序論
(11)
個人情報の知識
(36)
国際情勢
(1)
殺人鬼一覧
(156)
殺人事件
(50)
自動車
(2)
社会学
(1)
社会問題
(2)
趣味
(1)
宗教犯罪
(7)
小説の知識
(2)
親衛隊全国指導者ハイムリン・ヒムラー
(1)
図書館員のパソコン基本コース
(1)
世界のあれこれ
(3)
世界の国歌
(2)
政治
(1)
徴兵制度
(3)
哲学
(1)
読書
(1)
日本
(2)
犯罪学の研究
(11)
文化 海外
(1)
文系
(7)
勉強法
(7)
漫画
(9)
理系コンテンツ
(1)
料理
(49)
人気の投稿
-
片桐清二精神的に邪魔された片桐清二機長は日本航空350号機をエンジンの逆噴射により墜落させた。 24人の乗客が事故によって死ぬ。 彼は職業過失の疑いで逮捕されたが、彼は彼の精神衰退のために起訴されていない。 日本航空のフライト350は、日本の福岡発東京行きの国内線定期便で、...
-
2005年には、堀本紗也乃 (1993 - 2005年12月10日)は塾講師の萩野裕(1982年生まれ)によって殺害された。 小学校6年生の12歳は、京都府宇治の塾の先生で、同志社大学の学生で、23歳の萩野さんに不快感を与えた。 紗也乃は講師について彼女の両親に戸惑うようにな...
-
泥棒は殺人罪に直面する.死刑囚89人が逮捕され、2人が疑わしい-ジャパンタイムズ-2002年6月12日 佐賀県警は13日、刑務所に収容された佐賀県警の犯行捜査令状で、逮捕状を提出した。 北上の松江輝彦は、2年間の刑期を務めている。 逮捕令状は1989年1月、北方周辺の山々の崖の底...
-
ソ連(ロシア)の最も有名な連続殺人犯であるアンドレイ・チカチーロの息子、ユーリー・オドナチェヴァ(Yury Odnacheva)は、ウクライナの東部で殺害罪を犯したと逮捕された。 彼は2009年に数回殺人しようと逮捕された息子、ユリーを傷つけたにちがいない。父が父なら子も子。 ド...
-
彼はヴィンテージレッドのロールスロイス・シルバークラウド、フェラーリを運転した。 六本木周辺でフェラーリを運転していた彼は、西洋化した価値観を持つ好奇心が強い人物だった。 身長165センチという小柄で、背の高さが1.7mになるように、彼は靴のリフトを身に着け、人間の成長ホルモンを...
-
性的サディズムやマゾヒズムの起源を説明できる理論は今のところない。 一部の研究者は、生物学的要因の結果として、性的なパラフィリアの存在を説明しようとしている。 この視点の証拠は、性的犯罪者の神経心理学的検査および神経学的検査の異常所見に由来する 。 いくつかの研究者は、パラ...
-
宮崎勤、1962年8月21日生まれ、「オタク殺人犯」、「少女殺人者」、「ドラキュラ」は日本の連中殺人犯。 宮崎勤、1962年8月21日に東京の糸上町で、早く生まれた。 彼はわずか2.2kgの重さで、両手の関節が融合し、手首を上に曲げることができなくなった。 彼が5歳の時、同級生は...
-
小平義雄 (小平義雄小平義雄、1905年1月28日- 1949年10月5日)は強姦や連続殺人を犯した日本の兵士だ。 彼はまた、日本軍が第二次日中戦争以前に残虐行為をしたと言った数少ない元軍人の一人でもあった。 小平は幼少時に吃音に苦しんだ。 彼は1923年に日本国海軍に加わった。...
-
一部の外国メディアはまた、警察が事件の際にミスリリアを引き起こす可能性のある情報を報道機関に漏らしたと批判した。 しかし、日本が陪審を使用していないので、これは誤解の根拠になることはできない。 ブラックマンホステスの死-5月月曜日。-07、2001-By Evan Alan Wr...