パソコン活用研究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活用研究目次