行 | ::= | トークン [トークン …] | 行はトークンの並びとして認識できる | |
トークン | ::= | 数値|演算子 | トークンとは数値か演算子のどちらかである | |
演算子 | ::= | ‘+’ | ‘-’ | ’*’ | ‘/’| ‘%’ | 現時点では 四則演算と%だけを扱う | |
数値 | ::= | 数字 [数字 …] | ||
数字 | ::= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
上記の文法記述で、数字に 0 を含むかどうかについては検討を要する
また、上記の設計では、数値と演算子が交互に現れるという制約もなく、 例えば
+ 1 1 * 2 / +
というような(或いは右図のような)並びも式として受け入れられてしまうことになる。
これらを考慮して正確に数字を表すBNSの式はもう少し複雑なものになる(略)。
先頭から順々に計算を実行する
=>
1つ1つの計算は、以下のような手順で行う (配列の中に以下のものが並ぶことを想定する)
0 1 2 3...
数 演算子 数 演算子
最初の3つの要素を使う(配列から取り除いて用いる)
operand 1 (左被演算子)
operator (演算子)
operand 2 (右被演算子)
この3つを用いて計算を行い、結果を 再度 配列の先頭に戻す
配列の要素が1つになった時点で計算は終了
ここで使われている配列操作関数(shift, unshift)については、本ページ末尾近くの補足5を参照。
l=a.shift # この時点では l,r も String型
ope=a.shift
r=a.shift
result=calc1(ope,l,r) # calc はこれから作成する関数
a.unshift result # なので ここまでの段階ではプログラムが動かない
以上の操作を、
while a.length > 1 do
のループの中で行う
ここまででプログラムを動作させるためには
def calc1(op, v1, v2)
puts "calc1(#{op},#{v1},#{v2})"
end
のようなダミーのプログラムを挟んでおくといい
実際に加減乗除の演算を実行するにあたって、 通常の言語処理系のプログラムでは、それぞれの演算を 実行するプログラムを記述することは通常は行われない。
一般的なCPUはこれらの演算をハードウェアで行う回路(およびそのための 機械語命令)を装備しているので、それを呼び出すだけでいい。
=> ここまでの過程をプログラムの改良過程としてまとめたページを参照しつつ、 ここまで紹介したプログラムを1つのファイルにまとめたもの(12-1.rb)を 各自動かしてみて下さい。
ただし、プログラミング言語によっては do や exec
が「予約語」とされている
場合もあるので、今回その名前は避けた(ちゃんと調べなくてもそういうケースが
多いので無意識に避けている、というのが実情)。
なおrubyでは exec は予約語ではないが do は予約語。
予約語を使おうとするとエラーが出る例:
irb(main):001:0> do=1
SyntaxError: (src/irb):1: syntax error, unexpected keyword_do_block
from C:/Users/nobu/.pik/rubies/Ruby-200-p195/bin/irb:12:in `<main>'
irb(main):002:0>
パターンを増やす
\d+\.\d+
\d+(?:\.\d+)?
(?: ... )
は
グループを作るだけの括弧(キャプチャしない)\.
は、ワイルドキャラクタ .
を
\
でエスケープしたもの (ピリオドそのもの を表す)。
種類に応じて、データを適切な型に変換しておく
line.scan(%r![-+/*%]|\d+(?:\.\d+)?!).map{|e|
case e
when /\d+\.\d+/ then e.to_f
when /\d+/ then e.to_i
when %r![-+/*]! then e.to_sym
end
}
t.is_a?(Symbol)
t.is_a?(Numeric)
関数 scan にしてまとめておく
def scan(str)
...
end
ここ(12-2.rb)にここまでの完成版を置いたので参考にして下さい。
/ / の間 | 正規表現(%r記法でもいい(右下図)) | |
( ) の間 | 後方参照される $1 $2 など | |
数字を意味する | ||
[+-] | + または - 1文字にマッチ | |
.* | あと 残りぜんぶ |
データの追加、と、取り出し(それぞれ 前から、および後ろから)について、計4つのメソッドが用意されている
shift 先頭から取り出す pop 最後尾から取り出す
unshift 先頭にデータを挿入 push 最後尾にデータを追加
配列の各要素に対する繰り返し処理として、(a を配列として)
for e in a do puts e end
または
a.each do |e| puts e end
という書き方を以前に紹介したが、
上記のメソッドのうち shift を使って、
while x=a.shift do puts x end
という書き方もできる。