パソコン活用研究5番街(Visual Basic、Excel(VBA)、BASIC プログラミング研究)


VB.NETで計算機を作る(足し算引き算)
(準備中)


いよいよVB.NETで再帰を使って再帰下降パースによる演算を実装してみることにしますが。
まずは肩慣らしで足し算と引き算のみでやってみます。
※まだ、足し算、引き算にしか対応させていないので、乗算除算、括弧( )があると停止します。
自分の勉強用にコーディングしたものなので、ボロな個所が多数あります。

Cによる完成形のプログラムは「プログラミング言語を作る」のP57〜P70にあります。
本に掲載されているプログラムは、再帰下降パーサによる完成されたプログラムで、
字句解析ー>構文解析ー>演算まで2段抜かしでやってしまうようなプログラムです。

このページでは、各プロセスの動きを1段づつ確認したいので、本にあるような洗練されたものとはかなり違う
無骨なプログラムを作っていきます。加えて、上に書いたように乗算除算、括弧( )は対応してないので未完成形です。


[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

プログラミング言語を作る 前橋 和弥
価格:6,560円(税込、送料別) (2024/3/28時点)




「プログラミング言語を作る」(前橋和弥) Amazon


VB.NETで計算機を作る(字句解析)からの変更点、追加点
構造体変数 tokをグローバル変数にした。

再帰が使えるので、再帰下降パースで
演算部を実装してみた。
BASICで組んだ時は構文解析の工程 ―> 演算の工程 と2段階に
工程をはっきり分けていたが、再帰下降パースで実装すると、
構文解析しつつ演算もこなしていくことになる。
トークンを1つ先読みするので、LL(1)パーサということになる。

演算のための基本的な関数はexpression, primary_expression, get_tokenの3つ

expressionは v1+v2 またはv1-v2 という足し算、引き算を実行するが、
v1,v2の値はprimary_expressionを呼び出して取りに行く。
primary_expressionはget_tokenを呼び出して数値を取得する。

BNF記法で定義すると

expression: primaru_expression | expression + primary_expression | expression - primary expression;
primary _expression: number;

構文図式で表すと以下の通り


入力された式の末尾に式の終わりを示すために列挙型でtype.line_end を追加している。
tok(pos + 1).type = type.line_end

関数expression
上の構文図式の通り、まずprimary_expressionを呼び出し、そこで取得した値を変数v1に入れる。
以下whileループで、トークンを1つ読み、それが演算子+ ,- であれば再びprimary_expressionを呼び出して
次の値を取得しv2に入れ、演算を行うということをぐるぐる回している。

先読みしたトークンが+, - 以外の場合(このプログラムではtype.line_endの属性のトークンが該当する)には、
whileループを抜ける。

関数primary_expression
get_tokenを呼び出してトークンを読みだしてきてその値をリターンするだけ。
一応以下のIf文では、読みだしたトークンの属性がtype.number出ない場合は"Syntax error"を表示して
プログラム終了とするようにしているが、字句解析のパートでそういうイレギュラーな表記をある程度排除しているので
予期しない文字列(TOKEN)に出くわしてsyntax errorになることはない。
※ただし、入力を許容している* / ( ) などの解析には対応させていないので、これらの記号があると停止する。
If t.type = type.number Then Return t.v  Else Console.WriteLine("Syntax error") : End


関数 get_token() では
Return tok(i - 1)
として構造体tok()をリターンしている。

VB.Netでは構造体をリターンできる。




10--20+35 を実行してみたところ。
1文字づつに分解 ー>字句解析(TOKEN) ー>構文解析しながら演算(Calculate)
のプロセスを全部表示させている。


(実行画面1)



乗算除算にはまだ対応させていないので、乗算の記号*で停止する。
(実行画面2)


括弧( )への対応もしていないので、( )が出現するとSyntax errorと表示して停止する。

(実行画面3)

ソースコード

Imports System Module Program
REM Public num_token As Integer
Enum type number add_op sub_op mul_op div_op lparen rparen space bad line_end End Enum
Structure token Dim type As type Dim str As String Dim v As Integer End Structure
Public tok(20) As token
Sub Main(args As String()) Dim i, pos, isnum As Integer Dim value As Double Dim r As type Dim statement As String statement = Console.ReadLine()
For i = 0 To statement.Length - 1 Console.WriteLine("{0} : {1}", i, statement.Chars(i)) Next
Console.WriteLine("TOKEN") pos = 0 : isnum = 0
For i = 0 To statement.Length - 1 r = checktype(statement.Chars(i)) If r = type.space Then GoTo L1 : If r = type.bad Then Exit For If isnum = 1 And r = type.number Then tok(pos).str = tok(pos).str + statement.Chars(i) : tok(pos).v = Integer.Parse(tok(pos).str) : GoTo L1 If isnum = 1 And r = type.rparen Then pos = pos + 1 : tok(pos).str = statement.Chars(i) : tok(pos).type = r : GoTo L1 If isnum = 1 And (r = type.add_op Or r = type.sub_op Or r = type.mul_op Or r = type.div_op) Then pos = pos + 1 : tok(pos).str = statement.Chars(i) : tok(pos).type = r : pos = pos + 1 : isnum = 0 : GoTo L1 If isnum = 0 And r = type.lparen Then 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 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 L1: Next
tok(pos + 1).type = type.line_end
For i = 0 To pos + 1 Console.WriteLine("{0}: {1} : {2} : {3}", i, tok(i).str, tok(i).type, tok(i).v) Next
If r = type.bad Then Console.WriteLine("Wrong expression") Console.WriteLine("Calculate") value = expression() Console.WriteLine(value) End Sub
Function expression() As Double Dim v1, v2 As Double Dim t As token v1 = primary_expression() Console.WriteLine("expression v1:{0}", v1) While (1) t = get_token() Console.WriteLine("expression {0}: {1} : {2} ", t.str, t.type, t.v) If (t.type <> type.add_op And t.type <> type.sub_op) Then unget_token() : Exit While v2 = primary_expression() Console.WriteLine("expression v2:{0}", v2) If t.type = type.add_op Then v1 = v1 + v2 If t.type = type.sub_op Then v1 = v1 - v2 Console.WriteLine("expression calculated v1:{0}", v1) End While
Return v1 End Function
Function primary_expression() As Double Dim t As token t = get_token() Console.WriteLine("primary_expression {0}: {1} : {2} ", t.str, t.type, t.v) If t.type = type.number Then Return t.v  Else Console.WriteLine("Syntax error") : End
End Function
Function get_token() As token Static Dim i As Integer = 0 Console.WriteLine("get_token {0}: {1} : {2} :{3} ", i, tok(i).str, tok(i).type, tok(i).v) REM Console.WriteLine("get_token {0}: {1} : {2} :{3} ", num_token, t.str, t.type, t.v) i = i + 1 Return tok(i - 1) End Function
Sub unget_token()
End Sub
Function checktype(ByVal t As String) As Integer If IsNumeric(t) = True Then Return type.number 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.lparen If t = ")" Then Return type.rparen If t = " " Then Return type.space Return type.bad End Function
End Module






TopPage > Visual BasIc&Excel活用研究目次