VBScriptをつくってみる(制御構文編)

前回まででVBScriptの変数宣言機能・変数代入機能・演算機能を作りました。
今回は制御構文(If, For)を作ってみます。

まずは、制御構文に対応するように構文解析器を改良します。
VBScriptの文法を知っていればそんなに難しくない構文解析ルールですね。
一部TODOがありますが、そこは見なかったことにして下さい。

```
  block_statement : var_declartion
                  | if_statement
                  | loop_statement
                  | for_statement
                  | select_statement
                  | inline_statement nl

  # If Statement
  if_statement : 'If' expr 'Then' nl block_statement_list else_statement_list 'End' 'If' nl { result = AST::IfStatement.new(val[1], val[4], val[5]) }
               | 'If' expr 'Then' inline_statement else_opt end_if_opt nl                   { result = AST::IfStatement.new(val[1], val[3], val[4]) }
  else_statement_list : 'Elseif' expr 'Then' nl block_statement_list else_statement_list    { result = AST::IfStatement.new(val[1], val[4], val[5]) }
                      | 'Elseif' expr 'Then' inline_statement nl else_statement_list        { result = AST::IfStatement.new(val[1], val[3], val[5]) }
                      | 'Else' nl block_statement_list                                      { result = AST::ElseStatementList.new(val[2]) }
                      | 'Else' inline_statement nl                                          { result = AST::ElseStatementList.new(val[1]) }
                      |                                                                     { result = AST::Nop.new }
  else_opt : 'Else' inline_statement                                                        { result = val[1] }
           |                                                                                { result = AST::Nop.new }
  end_if_opt : 'End' 'If'
             |

  loop_statement : 'Do' 'While' expr nl block_statement_list 'Loop' nl { result = AST::WhileStatement.new(val[2], val[4]) }
                 | 'Do' 'Until' expr nl block_statement_list 'Loop' nl { result = AST::UntilStatement.new(val[2], val[4]) }
                 | 'Do' nl block_statement_list 'Loop' 'While' expr nl { result = AST::DoWhileStatement.new(val[5], val[2]) }
                 | 'Do' nl block_statement_list 'Loop' 'Until' expr nl { result = AST::DoUntilStatement.new(val[5], val[2]) }
                 | 'Do' nl block_statement_list 'Loop' nl { result = AST::Nop.new } # TODO: Exit文が実装されたらLoop文も実装する
                 | 'While' expr nl block_statement_list 'Wend' nl { result = AST::WhileStatement.new(val[1], val[3]) }

  for_statement : 'For' extended_id '=' expr 'To' expr step_opt nl block_statement_list 'Next' nl { result = AST::ForStatement.new(val[1], val[3], val[5], val[6], val[>
                | 'For' 'Each' extended_id 'In' expr nl block_statement_list 'Next' nl { result = AST::Nop.new } # TODO: 配列が実装されたらForEach文を実装する
  step_opt : 'Step' expr { result = val[1] }
           |             { result = AST::NumberLiteral.new(1) }

  # Select Statement
  select_statement : 'Select' 'Case' expr nl case_statement_list 'End' 'Select' nl  { result = AST::SelectStatement.new(val[2], val[4]) }
  case_statement_list : 'Case' expr nl_opt block_statement_list case_statement_list { result = val[4].unshift(AST::CaseStatement.new(val[1], val[3])) }
                      | 'Case' 'Else' nl_opt block_statement_list                   { result = [AST::CaseElseStatement.new(val[3])] }
                      |                                                             { result = [] }
```

次に各制御構文の実装を紹介します。
まずは、If文です。
条件式をevalし、その結果がTrueかFalseかに応じてThen節もしくはElse節の実行(eval)を行います。

```rb
  class IfStatement < List
    def initialize(expr, then_statement_list, else_statement_list)
      super([expr, then_statement_list, else_statement_list])
    end

    def eval(environment)
      if child(0).eval(environment)
        child(1).eval(environment)
      else
        child(2).eval(environment)
      end
    end
  end
```

While文は特に説明をしなくても自明かと思います。

```rb
  class WhileStatement < List
    def initialize(expr, block_statement_list)
      @expr = expr
      super([block_statement_list])
    end

    def eval(environment)
      while @expr.eval(environment) do
        child(0).eval(environment)
      end
    end
  end
```

最後にFor文の紹介です。
For文は引数が多いために実装がやや面倒くさいです。
To句やStep句には即値だけでなく式も書けることの考慮が必要です。

```rb

  class ForStatement < List
    def initialize(id, from, to, step, block_statement_list)
      @id = id
      @from = from
      @to = to
      @step = step
      super([block_statement_list])
    end

    def eval(environment)
      environment[@id.identifier] = @from.eval(environment)
      to = @to.eval(environment)
      step = @step.eval(environment)
      if step >= 0
        while environment[@id.identifier] <= to
          child(0).eval(environment)
          environment[@id.identifier] += step
        end
      else
        while environment[@id.identifier] >= to
          child(0).eval(environment)
          environment[@id.identifier] += step
        end
      end
    end
  end
```

ここまでの機能を使うことで、フィボナッチ数のN項目を計算できるようになりました。

```vb
' フィボナッチ数のn項目を計算する

Dim n, tmp1, tmp2, answer

n = 10

tmp1 = 1
tmp2 = 1

If n = 1 Or n = 2 Then
  answer = 1
Else
  For i = 1 To n - 2 Step 1
    answer = tmp1 + tmp2
    tmp1 = tmp2
    tmp2 = answer
  Next
End If

' この時点でanswerに答えがセットされている
```

怒涛のVBScript記事連投は、VBScriptっぽい文法を持ったチューリング完全な言語が完成したあたりで一旦中断します。
気が向いたら、また来年この続きから言語実装を始めるかもしれません。

明日のZOZOテクノロジーズアドベントカレンダー#3 @zt_takumi_ito さんです。
是非そちらもご覧ください！