目次
前
次
インタプリタ詳細設計
インタプリタを実現するための仕様が固まりました。
ステートメントを実行するために必要となる
手続き、関数を用意します。
手続き、関数を思いつくままにリストしてから
その仕様から詳細を設計していきます。
- 行初期化
- 変数初期化
- 配列初期化
- 変数、配列、関数、手続きの判別
- 関数IN
- 関数INP
- 手続きOUT
- 手続きOUTP
- 比較演算
各仕様を設計していきます。
具体的なコードは、Tcl/Tkで記述します。
行初期化
インタプリタで扱える行数を決めておき
ある程度の規模のプログラムコード実行
に耐えられるようにします。
今回は、最大4096行まで扱えるとします。
初期化は、行番号とステートメントに分けます。
コードは、以下。
for {set i 0} {$i < 4096} {incr i} {
set ls($i) -1
set ss($i) ""
}
行番号の連想配列名をlsに、ステートメントの
連想配列名を、ssとします。
行番号をラベルと考えると、連想配列で管理しておけば
GOTO、GOSUB等の分岐で、分岐先のステートメントを取得
しやすくなると考えました。
行番号は1から始めるとして、行番号の連想配列には
存在しない行番号の「−1」を設定。
変数初期化
変数は'A'から'Z'の26文字なので、連想配列の
キーを文字'A'から'Z'のいずれかにして、ゼロ
で初期化。
コードは、以下。
for {set i 0} {$i < 26} {incr i} {
# generate ascii code
set j [format "%c" [expr $i+65]]
# substitute
set xvar($j) 0
}
変数格納用の連想配列名を、xvarとします。
配列初期化
配列は<0>から<100>の101個なので、連想
配列のキーを<0>から<100>のいずれかにし
ゼロで初期化。
コードは、以下。
for {set i 0} {$i < 101} {incr i} {
# generate ascii code
set j "\<$i\>"
# substitue
set xarray($j) 0
}
変数格納用の連想配列名を、xvarとします。
変数、配列、関数、手続きの判別
命令(コマンド)の中で、LET、PRINTは変数、配列、関数を扱い
IF文、WHILE文、FOR文では変数、配列、関数、手続きを扱います。
個別に判別して全体が膨れ上がるので、まとめます。
変数の判別は、文字'A'から'Z'のいずれかを判断し
変数であれば1を返し、それ以外は0を返すと考え
ます。
proc is_variable {x} {
# default
set result 0
# judge
for {set i 0} {$i < 26} {incr i} {
# generate string
set symbol [format "%c" [expr $i+65]]
# compare
if { $symbol == $x } {
set result 1
}
}
return $result
}
配列の判別は、<0>から<100>のいずれかを
判断し、配列であれば1を返し、それ以外は0を返すと
考えます。
proc is_array {x} {
# default
set result 0
# judge
for {set i 0} {$i < 101} {incr i} {
# generate string
set symbol "\<$i\>"
# compare
if { $symbol == $x } {
set result 1
}
}
return $result
}
変数、配列の判別ができたとして、実際は数値を利用するので
変数、配列に格納されている数値を取り出す処理が必要です。
次のように実現すればよさそう。
proc get_value {x} {
#
global xvar xarray io_in_v io_out_v
# default
set result $x
# have operator
if { [have_operator $x] > 0 } {
set result [calc_asmdr $x]
}
# variable
if { [is_variable $x] > 0 } {
set result $xvar($x)
}
# array
if { [is_array $x] > 0 } {
set result $xarray($x)
}
# INP
if { [have_inp $x] > 0 } {
set result [get_decimal $io_in_v]
}
# IN
if { [have_in $x] > 0 } {
set result [in_handler $x]
}
return $result
}
入力関数IN、INPを含めて、1ビットの論理値と8ビット
の10進数で数値を取得できるようにしています。
1ビットの論理値を取得する関数は、in_handler。
8ビットの10進数で数値を取得する関数として
inp_handler
を用意。
10進数への変換操作は、次の関数が担当。
proc get_decimal {x} {
# copy
set xx $x
# clear
set result 0
# add
for {set i 0} {$i < 8} {incr i} {
# get target
set tmp [string index $xx $i]
# add
set result [expr $result * 2 + $tmp]
}
return $result
}
数式を含む場合の判断も必要になるので、関数を2つ
定義します。
数式の演算記号を含むかを判断する関数は以下。
proc have_operator {x} {
# default
set result 0
# copy
set xx $x
# judge
if { [llength [split $xx +]] > 1 } {
set result 1
}
if { [llength [split $xx -]] > 1 } {
set result 1
}
if { [llength [split $xx *]] > 1 } {
set result 1
}
if { [llength [split $xx /]] > 1 } {
set result 1
}
if { [llength [split $xx %]] > 1 } {
set result 1
}
if { [llength [split $xx &]] > 1 } {
set result 1
}
if { [llength [split $xx |]] > 1 } {
set result 1
}
if { [llength [split $xx ^]] > 1 } {
set result 1
}
return $result
}
数式の計算を実行する関数は以下。
proc calc_asmdr {x} {
# default
set result $x
# +(add)
if { [llength [split $x +]] > 1 } {
# split
set xx [split $x +]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# add
set result [expr $xcar + $xcdr]
}
# -(subtract)
if { [llength [split $x -]] > 1 } {
# split
set xx [split $x -]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# subtract
set result [expr $xcar - $xcdr]
}
# *(multiply)
if { [llength [split $x *]] > 1 } {
# split
set xx [split $x *]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# multiply
set result [expr $xcar * $xcdr]
}
# /(divide)
if { [llength [split $x /]] > 1 } {
# split
set xx [split $x /]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# divide
set result [expr $xcar / $xcdr]
}
# %(resident)
if { [llength [split $x %]] > 1 } {
# split
set xx [split $x %]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# add
set result [expr $xcar % $xcdr]
}
# &(logical and)
if { [llength [split $x &]] > 1 } {
# split
set xx [split $x &]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# logical and
set result [expr $xcar & $xcdr]
}
# |(logical or)
if { [llength [split $x |]] > 1 } {
# split
set xx [split $x |]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# logical or
set result [expr $xcar | $xcdr]
}
# ^(logical exclusive or)
if { [llength [split $x ^]] > 1 } {
# split
set xx [split $x ^]
# get parameters
set y [get_calc_param $xx]
# separate
set xcar [lindex $y 0]
set xcdr [lindex $y end]
# logical exclusive or
set result [expr $xcar ^ $xcdr]
}
return $result
}
数式処理では、2項演算に限定して、リストから
2パラメータを取り出す関数を使います。
proc get_calc_param {x} {
# first
set car [lindex $x 0]
# second
set cdr [lindex $x end]
# convert
set xcar [get_value $car]
set xcdr [get_value $cdr]
# concatenate
set result "$xcar $xcdr"
return $result
}
この関数が、加減乗除算と剰余算、論理演算で
利用する演算子を発見して、演算を一手にまと
めていることに。
関数IN
グローバル変数io_in_vに入力値が含まれるとします。
8ビットで表現した2進数の指定ビットから論理値を
取出してくる機能を持たせます。
IN(?)で利用するとして、?には数値、変数、配列、数式
を指定できます。
proc in_handler {x} {
global io_in_v
# get index
set xx [string index $x 3]
# judge
if { $xx == "<" } {
if { [string index $x 5] == ">" } {
set xv [get_value [string range $x 3 5]]
} else {
if { [string index $x 6] == ">" } {
set xv [get_value [string range $x 3 6]]
} else {
set xv [get_value [string range $x 3 7]]
}
}
} else {
set xv [get_value [string index $x 3]]
}
# copy
set result [string index $io_in_v [expr 7-$xv]]
return $result
}
MSBの位置を7とするために、ビット位置を7から減算。
関数INは、単独で使われることはなく、LET、PRINT、条件文
に含まれます。
関数INP
8ビットで表現した2進数をそのまま返す仕様とします。
パラメータなしでINP()として、グローバル変数io_in_vの
格納値を返す仕様で定義。
proc inp_handler {} {
#
global io_in_v
return [get_decimal $io_in_v]
}
10進数変換の関数を用意しておきます。
proc get_decimal {x} {
# copy
set xx $x
# clear
set result 0
# add
for {set i 0} {$i < 8} {incr i} {
# get target
set tmp [string index $xx $i]
# add
set result [expr $result * 2 + $tmp]
}
return $result
}
関数INPは、単独で使われることはなく、LET、PRINT、条件文
に含まれます。
手続きOUT
グローバル変数io_out_vに出力値が含まれるとします。
ビット位置と論理値の2パラメータを指定。
OUT(x,y)のようにxとしてビット位置、yとして論理値を指定。
x、yには数値、変数、配列、数式を使えるとします。
proc out_handler {x} {
#
global io_out_v
# get bit location
set bx [get_value [lindex $x 0]]
set bx [expr 7-$bx]
# get bit value
set bv [get_value [lindex $x end]]
#
set result ""
for {set i 0} {$i < 8} {incr i} {
# get target bit
set bb [string index $io_out_v $i]
# replace
if { $i == $bx } {
set bb $bv
}
# append
set result "$result$bb"
}
# store
set io_out_v $result
}
手続きOUTP
グローバル変数io_out_vに値を設定すれば処理は終了。
パラメータとして、変数、配列、2進数、16進数を指定
できるようにします。
2進数は文字列ですが、16進数は#をつけて判断。
proc outp_handler {x} {
#
global io_out_v
# copy
set xx $x
# judge binary
if { [string index $xx 0] == "\'" } {
set y [get_decimal [string range $xx 1 8]]
set ud [dec2bin [expr $y / 16]]
set ld [dec2bin [expr $y % 16]]
set yy "$ud$ld"
}
# judge hexadecimal
if { [string index $xx 0] == "#" } {
set ud [hex2bin [string index $xx 1]]
set ld [hex2bin [string index $xx 2]]
set yy "$ud$ld"
}
# judge variable
if { [is_variable $x] > 0 } {
set y [get_value $x]
set ud [dec2bin [expr $y / 16]]
set ld [dec2bin [expr $y % 16]]
set yy "$ud$ld"
}
# judge array
if { [is_array $x] > 0 } {
set y [get_value $x]
set ud [dec2bin [expr $y / 16]]
set ld [dec2bin [expr $y % 16]]
set yy "$ud$ld"
}
# store
set io_out_v $yy
}
10進数から2進数に変換するため、関数dec2binを用意。
proc dec2bin {x} {
# default
set result "0000"
# judge
if { $x == 1 } {
set result "0001"
}
if { $x == 2 } {
set result "0010"
}
if { $x == 3 } {
set result "0011"
}
if { $x == 4 } {
set result "0100"
}
if { $x == 5 } {
set result "0101"
}
if { $x == 6 } {
set result "0110"
}
if { $x == 7 } {
set result "0111"
}
if { $x == 8 } {
set result "1000"
}
if { $x == 9 } {
set result "1001"
}
if { $x == 10 } {
set result "1010"
}
if { $x == 11 } {
set result "1011"
}
if { $x == 12 } {
set result "1100"
}
if { $x == 13 } {
set result "1101"
}
if { $x == 14 } {
set result "1110"
}
if { $x == 15 } {
set result "1111"
}
return $result
}
16進数から2進数に変換するため、関数hex2binも用意。
proc hex2bin {x} {
# default
set result "0000"
# judge
if { $x < 10 } {
set result [dec2bin $x]
}
if { $x == "A" || $x == "a" } {
set result "1010"
}
if { $x == "B" || $x == "b" } {
set result "1011"
}
if { $x == "C" || $x == "c" } {
set result "1100"
}
if { $x == "D" || $x == "d" } {
set result "1101"
}
if { $x == "E" || $x == "e" } {
set result "1110"
}
if { $x == "F" || $x == "f" } {
set result "1111"
}
return $result
}
比較演算
条件文の中で演算子の左右に、数値、変数、配列、関数の
出力値を置いて比較した結果、真偽を1、0で返します。
演算子の左右にあるパラメータを取り出し、数値にしてから
比較していきます。数値を取得するのに、関数get_valueを
使えばよいはず。
proc get_condition_value {x} {
# default
set result 0
#
set first [lindex $x 0]
set xop [lindex $x 1]
set second [lindex $x end]
# judge
if { $xop == "=" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car == $cdr } {
set result 1
}
}
if { $xop == "<>" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car != $cdr } {
set result 1
}
}
if { $xop == ">=" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car >= $cdr } {
set result 1
}
}
if { $xop == ">" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car > $cdr } {
set result 1
}
}
if { $xop == "<=" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car <= $cdr } {
set result 1
}
}
if { $xop == "<" } {
set car [get_value $first]
set cdr [get_value $second]
if { $car < $cdr } {
set result 1
}
}
return $result
}
ここまでで、インタプリタの動作に必要な手続き、関数を
定義でたので、ステートメントとして利用できる命令を
確認しておきます。
ステートメントの先頭になる命令(コマンド)は以下。
- LET
- PRINTまたは?
- IF
- WHILE
- WEND
- FOR
- NEXT
- GOTO
- GOSUB
- OUT
- OUTP
- END
関数IN、INPは、LETあるいはPRINTの中で
利用できる仕様です。
目次
前
次