パソコン活用研究5番街(Visual
Basic、Excel(VBA)、BASIC プログラミング研究)
VB.NETで計算機を作る(LR解析)
(準備中)
今までに再帰下降パーサによる構文解析で計算機プログラムを作成してきました。
(→ひとまずの完成形はVB.netで計算機を作る(とりあえず完成形2)
その次にはいささか酔狂でLL構文解析表による計算機プログラムを作成してみました。
→VB.netで計算機を作る(LL解析)
今回はLR解析表を使った四則演算の計算機を作ってみることにしますが、
使えるのは加算と乗算だけです。一応( )は使えます。
実行例
スタックの表記の意味は姉妹サイト「構文解析(上昇解析)」をご参照下さい。
変数
token型構造体として、tok(), stack()をグローバル変数として宣言しています。
tok()は読み込み記号列用の変数、stack()はスタック用です。
宣言部と字句解析部
宣言部と字句解析部はVB.netで計算機を作る(とりあえず完成形2)やVB.netで計算機を作る(LL解析)
で作成したプログラムをほぼそのまま流用しています。使用する変数等の違いで多少の違いはありますが。
Enum typeにstateを追加しています。
@のForループでは、入力文字列(記号列)を1文字づつ表示
statement.Length
文字列statementの長さを取得するのに、Lengthプロパティを使っている。
AのForループでは、入力文字列をに分解しtokenに分解したものを表示
Imports System Module Program REM Public num_token As Integer Public err, cnt As Integer Enum type number dot add_op sub_op mul_op div_op pow_op lparen rparen space bad line_end state End Enum Structure token Dim type As type Dim str As String Dim v As Double End Structure Public tok(30), stack(30) As token Sub Main(args As String()) Dim i, j, pos, isnum, rp, lp As Integer Dim value As Double Dim r As type Dim statement, stck, calclist(20) As String REM num_token = 0 err = 0 : rp = 0 : lp = 0 statement = Console.ReadLine() For i = 0 To statement.Length - 1 Console.WriteLine("{0} : {1}", i, statement.Chars(i)) Next pos = 0 : isnum = 0 For i = 0 To statement.Length - 1 Try r = checktype(statement.Chars(i)) If (isnum = 0 Or isnum = 5) And r = type.space Then GoTo L1 : If (isnum = 1 Or isnum = 3) And r = type.space Then isnum = 5 : GoTo L1 : If r = type.bad Then Console.WriteLine("{0}:Bad character", i) : tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).type = type.bad : err = 1 : Exit For If (isnum = 1 Or isnum = 3) And r = type.number Then tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).v = Double.Parse(tok(pos).str) : GoTo L1 If isnum = 1 And r = type.dot Then tok(pos).str = tok(pos).str + statement.Chars(i) : isnum = 2 : GoTo L1 If (isnum = 1 Or isnum = 3 Or isnum = 5) And r = type.rparen Then rp = rp + 1 : pos = pos + 1 : tok(pos).str = statement.Chars(i) : tok(pos).type = r : isnum = 5 : GoTo L1 If (isnum = 1 Or isnum = 3 Or isnum = 5) And (r = type.add_op Or r = type.sub_op Or r = type.mul_op Or r = type.div_op Or r = type.pow_op) Then pos = pos + 1 : tok(pos).str = statement.Chars(i) : tok(pos).type = r : pos = pos + 1 : tok(pos).type = type.bad : isnum = 0 : GoTo L1 If isnum = 2 And r = type.number Then tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).v = Double.Parse(tok(pos).str) : isnum = 3 : GoTo L1 If isnum = 2 And r <> type.number Then err = 1 : Console.WriteLine("{0}: Bad expression", i) : Exit For If isnum = 3 And r = type.dot Then err = 1 : Console.WriteLine("{0}: too many dot", i) : tok(pos).type = type.bad : Exit For If isnum = 0 And r = type.lparen Then lp = lp + 1 : tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).type = r : pos = pos + 1 : GoTo L1 If isnum = 0 And r = type.number Then tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).type = type.number : tok(pos).v = Integer.Parse(tok(pos).str) : isnum = 1 : GoTo L1 If isnum = 0 And r = type.sub_op Then tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).type = type.number : isnum = 1 : GoTo L1 Console.WriteLine("{0}:Bad expression", i) : tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).type = type.bad : err = 1 : Exit For Catch ex As System.FormatException ' (0)0のような表現で")0"がSystem.FormatExceptionを引きおこすが、rparenの処理後isnum=5にすることで解消できる。tok(pos).v = Integer.Parse(tok(pos).str)が")0"という表現を整数型にパースできないため err = 1 Console.WriteLine("System.FormatException") Exit For End Try L1: If lp < rp Then Console.WriteLine("too many rparen") : err = 1 : Exit For Next If lp <> rp Then Console.WriteLine("The number of parentheses does not match.") : err = 1 Console.WriteLine("TOKEN") tok(pos + 1).type = type.line_end tok(pos + 1).str = "$" For i = 0 To pos + 1 Console.WriteLine("{0}: {1} : {2} : {3}", i, tok(i).str, tok(i).type, tok(i).v) Next If err = 1 Then Console.WriteLine("This is a bad expression") : GoTo L3
構文解析と演算
以下構文解析を行っている個所になります。
stack 姉妹サイト構文解析(上昇解析)の解析表のスタックに該当するものです。token型の構造体なので、メンバ変数としてtype,str,vを
持っています。
LR解析表を2次元の配列にしたりすると、LR解析表を使っている感がよくでてくると思いますが、ここではLR解析表の内容をSelect
Caseとif文で
直接ブレイクダウンして処理しています。
stckというのが状態を表す変数です。
stckの値により、Select Caseで場合分けしていますが、これがLR解析表の状態に該当します。
各状態の中で、if文でtok(i)により処理を分岐させています。tok(i)は次に読まれる記号を保持しています。
シフトの処理、還元の処理はそれぞれ関数shufy(), reduce()を呼んで処理しています。
変数iはtok()のindexとして使用
グローバル変数cntはstackのtopを表す変数として使っています。
関数shift()からの戻り値は次に読み込むtokuのindexを返し、関数reduce()からの返り値は、還元処理で削除するスタックの記号列の数です。
よってcnt=cnt-reduce() は還元処理後のstackのtopを示すことになります。
LR解析の場合は、解析しながら演算も同時進行していますので、解析が終了したときには、演算結果も出ており
stack(1).vに結果が保持されています。
演算は還元処理の時、すなわち関数reduce()で行われています。
このあたり、グローバル変数を使ったり、関数の戻り値を使ったりしょぼいコードの書き方になってますので、ご注意下さい。
stack(0).type = type.state : stack(0).str = "0" stck = stack(0).str ' cntはstackのtop iは読み込んでいるtokのindex i = 0 cnt = 0 ' Console.ReadLine() While (1) For j = 0 To cnt Console.Write("{0}", stack(j).str) Next Console.WriteLine(" : {0}", tok(i).str) ' Console.ReadLine() Select Case stck Case "0" If tok(i).type = type.number Then i = shift(i, "5") : stck = "5" : Console.WriteLine("s5") : Continue While If tok(i).type = type.lparen Then i = shift(i, "4") : stck = "4" : Console.WriteLine("s4") : Continue While Case "1" If tok(i).type = type.add_op Then i = shift(i, "6") : stck = "6" : Console.WriteLine("s6") : Continue While If tok(i).type = type.line_end Then Exit While Case "2" If tok(i).type = type.add_op Then Console.WriteLine("r2") : cnt = cnt - reduce(cnt, 2) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While If tok(i).type = type.mul_op Then i = shift(i, "7") : stck = "7" : Console.WriteLine("s7") : Continue While If tok(i).type = type.rparen Then Console.WriteLine("r2") : cnt = cnt - reduce(cnt, 2) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While If tok(i).type = type.line_end Then Console.WriteLine("r2") : cnt = cnt - reduce(cnt, 2) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While Case 3 If (tok(i).type = type.add_op) Or (tok(i).type = type.mul_op) Or (tok(i).type = type.rparen) Or (tok(i).type = type.line_end) Then Console.WriteLine("r4") : cnt = cnt - reduce(cnt, 4) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While Case 4 If tok(i).type = type.number Then i = shift(i, "5") : stck = "5" : Console.WriteLine("s5") : Continue While If tok(i).type = type.lparen Then i = shift(i, "4") : stck = "4" : Console.WriteLine("s4") : Continue While Case 5 If tok(i).type = type.number Then i = shift(i, "5") : stck = "5" : Console.WriteLine("s5") : Continue While If (tok(i).type = type.add_op) Or (tok(i).type = type.mul_op) Or (tok(i).type = type.rparen) Or (tok(i).type = type.line_end) Then Console.WriteLine("r6") : cnt = cnt - reduce(cnt, 6) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While Case 6 If tok(i).type = type.number Then i = shift(i, "5") : stck = "5" : Console.WriteLine("s5") : Continue While If tok(i).type = type.lparen Then i = shift(i, "4") : stck = "4" : Console.WriteLine("s4") : Continue While Case 7 If tok(i).type = type.number Then i = shift(i, "5") : stck = "5" : Console.WriteLine("s5") : Continue While If tok(i).type = type.lparen Then i = shift(i, "4") : stck = "4" : Console.WriteLine("s4") : Continue While Case 8 If tok(i).type = type.add_op Then i = shift(i, "6") : stck = "6" : Console.WriteLine("s6") : Continue While If tok(i).type = type.rparen Then i = shift(i, "11") : stck = "11" : Console.WriteLine("s11") : Continue While Case 9 If (tok(i).type = type.add_op) Or (tok(i).type = type.rparen) Or (tok(i).type = type.line_end) Then Console.WriteLine("r1") : cnt = cnt - reduce(cnt, 1) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While If tok(i).type = type.mul_op Then i = shift(i, "7") : stck = "7" : Console.WriteLine("s7") : Continue While Case 10 If (tok(i).type = type.add_op) Or (tok(i).type = type.mul_op) Or (tok(i).type = type.rparen) Or (tok(i).type = type.line_end) Then Console.WriteLine("r3") : cnt = cnt - reduce(cnt, 3) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While Case 11 If (tok(i).type = type.add_op) Or (tok(i).type = type.mul_op) Or (tok(i).type = type.rparen) Or (tok(i).type = type.line_end) Then Console.WriteLine("r5") : cnt = cnt - reduce(cnt, 5) : stck = goto_table(cnt) : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = stck : Continue While End Select End While If (err Mod 10) = 1 Then Console.WriteLine("Error on tokenization") : GoTo L3 If err >= 10 Then Console.WriteLine("Error on calculation") : GoTo L3 If lp <> rp Then Console.WriteLine("The number of parentheses does not match.") : GoTo L3 Console.WriteLine("Caluculation") value = stack(1).v Console.WriteLine("value: {0}", value)
関数shift() と関数reduce()
関数shift()は読み込んだ記号をスタックに追加していくという処理になります。
cnt = cnt + 1 : stack(cnt) = tok(i)
数値の場合はスタックの記号を
非終端記号"n"にしておきます。
If stack(cnt).type = type.number Then stack(cnt).str = "n"
更に新たな状態をスタックに追記し、その状態(変数i)をReturnします。
i = i + 1 : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = r
Function shift(ByVal i As Integer, ByVal r As String) As Integer cnt = cnt + 1 : stack(cnt) = tok(i) If stack(cnt).type = type.number Then stack(cnt).str = "n" i = i + 1 : cnt = cnt + 1 : stack(cnt).type = type.state : stack(cnt).str = r Return i End Function Function reduce(ByVal cnt As Integer, ByVal r As Integer) As Integer Dim v As Double Select Case r Case 1 v = stack(cnt - 5).v + stack(cnt - 1).v stack(cnt - 5).v = v cnt = cnt - 5 Return 5 Case 2 stack(cnt - 1).str = "E" cnt = cnt - 1 Return 1 Case 3 v = stack(cnt - 5).v * stack(cnt - 1).v stack(cnt - 5).v = v cnt = cnt - 5 Return 5 Case 4 stack(cnt - 1).str = "T" cnt = cnt - 1 Return 1 Case 5 v = stack(cnt - 3).v stack(cnt - 5).str = "F" stack(cnt - 5).v = v stack(cnt - 5).type = type.number Return 5 Case 6 stack(cnt - 1).str = "F" cnt = cnt - 1 Return 1 End Select End Function
goto表
r 還元された後のスタックにある状態
Function goto_table(ByVal cnt As Integer) As String Dim r As String r = stack(cnt - 1).str Select Case r Case "0" If stack(cnt).str = "E" Then r = "1" If stack(cnt).str = "T" Then r = "2" If stack(cnt).str = "F" Then r = "3" Case "4" If stack(cnt).str = "E" Then r = "8" If stack(cnt).str = "T" Then r = "2" If stack(cnt).str = "F" Then r = "3" Case "6" If stack(cnt).str = "T" Then r = "9" If stack(cnt).str = "F" Then r = "3" Case "7" If stack(cnt).str = "F" Then r = "10" End Select Console.WriteLine("goto ->{0} ", r) Return r End Function
字句解析のための関数checktype() とEnd Module
ここはVB.netで計算機を作る(とりあえず完成形2)
のものをそのまま流用しています。
このプログラムは加算と乗算だけなので、"-"やら"/"などは字句解析に不要ですが
そのまま残してあります。(今後の万が一拡張するときのため)
最後の1行はこのモジュールの終了をあらわすEnd Module
Function checktype(ByVal t As String) As Integer If IsNumeric(t) = True Then Return type.number If t = "." Then Return type.dot If t = "+" Then Return type.add_op If t = "-" Then Return type.sub_op If t = "*" Then Return type.mul_op If t = "/" Then Return type.div_op If t = "^" Then Return type.pow_op If t = "(" Then Return type.lparen If t = ")" Then Return type.rparen If t = " " Then Return type.space Return type.bad End Function End Module
TopPage > Visual BasIc&Excel活用研究目次