電卓プログラムから考える計算式のモデル(Ruby, 字句解析, 構文解析)

「モデルで考える」ことを弊社のチームで普段から強調されています。
最近ようやく意味がわかってきたので、電卓プログラムを例にまとめてみます。

1.計算式のモデルを定義
2.計算機クラスの作成
3.字句解析・構文解析プログラム
と段階を踏んで書いていきます。
※単項演算は未対応です。。。

1.計算式のモデルを定義
・計算式は、二項演算と値で表現される。
・二項演算は、 加算・減算・乗算・除算で構成される。
・演算する二項はexpr_leftとexpr_rightと名付ける
この3点を以下のようなコードで表します。

モデルは、定義したクラスの関連のみを表現するだけで、
実際どのように計算するかについては語られません。

モデルを定義し、アルゴリズムと分けることで、
モデルを特定の用途以外に用いることができます。
計算式の場合は、計算以外の用途が思い浮かびにくいですが。。。

私はモデルという用語をRuby on Railsから知ったので、
データベースからデータを引っ張ってくるクラスというイメージに支配されていました。
ですが、どうやらモデルとは、
物事の関連性をシンプルに表した物のようです。

 #計算式のモデルを定義

class Expression
end

class BinaryOperator < Expression
 attr_reader :expr_left, :expr_right

 def initialize(expr_left, expr_right)
  @expr_left = expr_left
  @expr_right = expr_right
 end
end

class Value < Expression
 attr_reader :value

 def initialize(value)
  @value = value
 end
end

class Add < BinaryOperator
end

class Subtract < BinaryOperator
end

class Multiply < BinaryOperator
end

class Divide < BinaryOperator
end

2.計算機クラスの作成
ここで、ようやく計算を行います。
既に構文解析がなされた式を引数として、
加算・減算・乗算・除算を行います。

class Calculator
    def execute(expr)
        case expr
            when Add
                execute(expr.expr_left) + execute(expr.expr_right)

            when Subtract
                execute(expr.expr_left) - execute(expr.expr_right)

            when Multiply
                execute(expr.expr_left) * execute(expr.expr_right)

            when Divide
                execute(expr.expr_left) / execute(expr.expr_right)

            when Value
                expr.value

            else
                raise 'Unknown Expression'
        end
    end
end

3.字句解析・構文解析プログラム
字句解析では、
RubyのライブラリにあるStringScannerを使いました。

# 字句解析
require 'strscan'

class Tokenizer
    def initialize(str)
        @scanner = StringScanner.new(str)
    end

    def tokenize
        val = @scanner.scan(/
                            [0-9]+
                       |     \(
                       |     \)
                       |     \+
                       |     \-
                       |     \*
                       |     \/
                      /x)
    end
end

## 構文解析
# expr = term
#     もしくは
# expr = expr +(-) term
#
# term = factor
#     もしくは
# term = term *(/) factor
#
# factor = value
#     もしくは
# factor = ( expr )
#
#
class Parser
    def parse(str)
        @tokenizer = Tokenizer.new(str)
        fetch
        expr
    end

    #トークンの先読み
    def fetch
        @lookahead = @tokenizer.tokenize
    rescue
        @lookahead = nil
    end

    def expr
        t = term
        while %w(+ -).include?(@lookahead)
            case @lookahead
                when '+'
                    fetch
                    t = Add.new(t, term)

                when '-'
                    fetch
                    t = Subtract.new(t, term)
            end
        end
        t
    end

    def term
        f = factor
        while %w(* /).include?(@lookahead)
            case @lookahead
                when '*'
                    fetch
                    f = Multiply.new(f, factor)
                when '/'
                    fetch
                    f = Divide.new(f, factor)
            end
        end
        f
    end

    def factor
        v = nil

        case @lookahead
            when '-'
                v = factor

            when '('
                fetch
                v = expr
                if @lookahead != ')'
                    raise 'Expected )'
                end
                fetch

            when /[0-9]+/
                v = Value.new(@lookahead.to_i)
                fetch

            when /[a-z]/
                v = Variable.new(@lookahead)
                fetch

            else
                raise 'Unknown token'
        end

        v
    end
end

parser = Parser.new
vm = Calculator.new

#計算式の入力 => 実行
until STDIN.eof?
    expr = parser.parse(readline)
    puts vm.execute(expr)
end

モデルについて理解が深まったので、
生かしていきたいです。

抽象化と分類

今日は、抽象化と分類について書いてみたいと思います。

抽象化をすると、思考にレバレッジが効くといいますか、いろいろなことに考え方を適用できるのでとても便利ですよね。

今日は抽象化ってそもそもなんだ?ということについて考えてみたいと思います。

続きを読む 抽象化と分類

ステージングサーバーで、mailcatcher をMTAとして使用する設定(Centos7 nginx)

ウェブサイトの立ち上げの際に、メール機能のテストに便利なのが mailcatcher 。

これを、ローカルではなく、ステージングサーバーでメインのMTAとして動かすようにしてみたので、その際の設定を書いてみる。

前提として、システムワイドにインストールしたrbenv上にrubyをインストールしています。

続きを読む ステージングサーバーで、mailcatcher をMTAとして使用する設定(Centos7 nginx)