LoginSignup
5
0

More than 3 years have passed since last update.

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

Posted at

前回までで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)を行います。

  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文は特に説明をしなくても自明かと思います。

  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句には即値だけでなく式も書けることの考慮が必要です。


  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項目を計算できるようになりました。

' フィボナッチ数の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 さんです。
是非そちらもご覧ください!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0