LoginSignup
0

More than 3 years have passed since last update.

posted at

updated at

Organization

VBScriptを作ってみる(構文解析編・代入文まで)

さて、前日に字句解析した結果を構文解析器に入力することで構文解析をしましょう。
言語実装をするときの大きな山の1つが構文解析器の実装です。
字句解析同様に、一から実装するということはせず、ライブラリを利用します。

RubyにはraccというLALR(1)パーサージェネレーターが付属しているので、これを使います。
yaccに似た書式なので、yaccを使ったことがある方ならば、ある程度雰囲気で読めるかと思います。
yaccもraccも使ったことがない場合は、以下のオンラインマニュアルが一番詳しくまとまっているので、以下の記事を読む前に一読することをおすすめします。

Racc User Manual

経験的に構文解析ルールを一気に書くと、conflictが大量に発生して辛い思いをすることが多いので、一旦小さなマイルストーンを決めることにします。
まずは、変数宣言と変数代入文が動くところまでを目指します。

具体的には以下のスクリプトを実行できれば良しとします。

Dim a, b
a = 10
b = (a + 1) * 3

' この時点でaに10がbに33が代入されている

VBScriptの文法とにらめっこすることで、以下の構文解析ルールを得ます。

# vim:filetype=ruby

class TinyVbsParser

start program

rule
  program : nl_opt global_statement_list { result = val[1] }

  nl_opt : nl
         |
  nl : NEWLINE nl
     | NEWLINE

  global_statement_list : global_statement { result = AST::Program.new(val[0]) }
                        | global_statement_list global_statement { result = result.add_child(val[1]) }
  global_statement : option_explicit
                   | block_statement

  option_explicit : 'Option' 'Explicit' nl { result = AST::OptionExplicit.new }
  block_statement : var_declartion
                  | inline_statement nl

  var_declartion : 'Dim' var_name other_vars_opt nl { result = AST::Dim.new([val[1]] + val[2]) }
  other_vars_opt : ',' var_name other_vars_opt { result = val[2] + [val[1]] }
                 | { result = [] }
  var_name : extended_id

  inline_statement : assign_statement

  assign_statement : left_expr '=' expr { result = AST::AssignStatement.new(val[0], val[2]) }

  left_expr : qualified_id { result = AST::LeftExpr.new(val[0]) }
  qualified_id : ID        { result = AST::Identifier.new(val[0]) }
  extended_id : safe_keyword_id
              | ID { result = AST::Identifier.new(val[0]) }
  safe_keyword_id : 'Option' { result = AST::Identifier.new(val[0]) }
                  | 'Step' { result = AST::Identifier.new(val[0]) }

  expr : imp_expr
  imp_expr : imp_expr 'Imp' eqv_expr { result = AST::BinaryExpr.new('Imp', val[0], val[2]) }
           | eqv_expr
  eqv_expr : eqv_expr 'Eqv' xor_expr { result = AST::BinaryExpr.new('Eqv', val[0], val[2]) }
           | xor_expr
  xor_expr : xor_expr 'Xor' or_expr { result = AST::BinaryExpr.new('Xor', val[0], val[2]) }
           | or_expr
  or_expr : or_expr 'Or' and_expr { result = AST::BinaryExpr.new('Or', val[0], val[2]) }
          | and_expr
  and_expr : and_expr 'And' not_expr { result = AST::BinaryExpr.new('And', val[0], val[2]) }
           | not_expr
  not_expr : 'Not' compare_expr { result = AST::UnaryExpr.new('Not', val[1]) }
           | compare_expr
  compare_expr : compare_expr '>=' concat_expr { result = AST::BinaryExpr.new('>=', val[0], val[2]) }
               | compare_expr '<=' concat_expr { result = AST::BinaryExpr.new('<=', val[0], val[2]) }
               | compare_expr '>' concat_expr { result = AST::BinaryExpr.new('>', val[0], val[2]) }
               | compare_expr '<' concat_expr { result = AST::BinaryExpr.new('<', val[0], val[2]) }
               | compare_expr '<>' concat_expr { result = AST::BinaryExpr.new('<>', val[0], val[2]) }
               | compare_expr '=' concat_expr { result = AST::BinaryExpr.new('=', val[0], val[2]) }
               | concat_expr
  concat_expr : concat_expr '&' add_expr { result = AST::BinaryExpr.new('&', val[0], val[2]) }
              | add_expr
  add_expr : add_expr '+' mod_expr { result = AST::BinaryExpr.new('+', val[0], val[2]) }
           | add_expr '-' mod_expr { result = AST::BinaryExpr.new('-', val[0], val[2]) }
           | mod_expr
  mod_expr : mod_expr 'Mod' mult_expr { result = AST::BinaryExpr.new('Mod', val[0], val[2]) }
           | mult_expr
  mult_expr : mult_expr '*' unary_expr { result = AST::BinaryExpr.new('*', val[0], val[2]) }
            | mult_expr '/' unary_expr { result = AST::BinaryExpr.new('/', val[0], val[2]) }
            | unary_expr
  unary_expr : '+' exp_expr { result = AST::UnaryExpr.new('+', val[1]) }
             | '-' exp_expr { result = AST::UnaryExpr.new('-', val[1]) }
             | exp_expr
  exp_expr : value '^' exp_expr { result = AST::BinaryExpr.new('^', val[0], val[2]) }
           | value
  value : const_expr
        | left_expr
        | '(' expr ')' { result = val[1] }
  const_expr : int_literal { result = AST::NumberLiteral.new(val[0]) }
             | bool_literal { result = AST::BoolLiteral.new(val[0]) }
             | STRING_LITERAL { result = AST::StringLiteral.new(val[0]) }
             | nothing
  int_literal : INT_LITERAL
              | HEX_LITERAL
              | OCT_LITERAL
  bool_literal : 'True' | 'False'
  nothing : null
          | empty
  null : 'Null' { result = AST::NullLiteral.new }
  empty : 'Empty' { result = AST::EmptyLiteral.new }
end

---- header
require_relative 'ast'
require_relative 'tiny_vbs.rex'

VBScriptでは代入は式ではなく文であるということが理解できていれば、比較的シンプルな構文解析ルールです。

また、要素を還元したときのルール中に AST::Hoge クラスが多く登場しますが、一旦は以下の以下の抽象クラスを継承している具象クラスであるという理解でも問題ないです。
デザインパターンでいうところのインタープリターパターンです。

module AST
  class Node
    def eval(environment)
      raise 'not implemented'
    end
  end

  class Leaf < Node
  end

  class List < Node
    def initialize(children)
      @children = children
    end

    def children
      @children
    end

    def child(index)
      @children[index]
    end

    def add_child(child)
      @children.push(child)
      self
    end

    def add_child_head(child)
      @children.unshift(child)
      self
    end
  end

さて、これで構文解析に必要なものは揃ったので、最後はVBScriptのプログラムを構文解析器に入力する部分を作ればOKです。

program = ARGF.read
parser = TinyVbsParser.new
ast = parser.scan(program)
pp ast

上記のプログラムを実行することで、以下の抽象構文木(AST)を得ることが出来ます。

#<AST::Program:0x00007f7f2a81f280
 @children=
  [#<AST::Dim:0x00007f7f2a81f2f8
    @var_names=
     [#<AST::Identifier:0x00007f7f2a81ff00 @identifier="a">,
      #<AST::Identifier:0x00007f7f2a81f6e0 @identifier="b">]>,
   #<AST::AssignStatement:0x00007f7f2a81ece0
    @children=
     [#<AST::LeftExpr:0x00007f7f2a81f190
       @children=[],
       @identifier=#<AST::Identifier:0x00007f7f2a81f1e0 @identifier="a">>,
      #<AST::NumberLiteral:0x00007f7f2a81edd0 @value=10>]>,
   #<AST::AssignStatement:0x00007f7f2a81da98
    @children=
     [#<AST::LeftExpr:0x00007f7f2a81eb50
       @children=[],
       @identifier=#<AST::Identifier:0x00007f7f2a81eba0 @identifier="b">>,
      #<AST::BinaryExpr:0x00007f7f2a81db10
       @children=
        [#<AST::BinaryExpr:0x00007f7f2a81e038
          @children=
           [#<AST::LeftExpr:0x00007f7f2a81e600
             @children=[],
             @identifier=
              #<AST::Identifier:0x00007f7f2a81e650 @identifier="a">>,
            #<AST::NumberLiteral:0x00007f7f2a81e290 @value=1>],
          @operator="+">,
         #<AST::NumberLiteral:0x00007f7f2a81dc50 @value=3>],
       @operator="*">]>]>

@children で表現されているインスタンス毎の入れ子構造を見ると、正しく構文解析が出来ていることがわかります。

次回はこのASTオブジェクトに対する eval メソッドの実行を行います。

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
What you can do with signing up
0