Hatena::Groupxn--272ax3f

スクリプト言語の作り方7日目

スクリプト言語の作り方7日目

プレゼンテーション

関数を使えるようにする

7.1関数サンプル

def fact(n) {
  f = 1
  while n > 0 {
    f = f * n
    n = n - 1
  }
  f
}
fact(9)
farc 9

7.1サンプル解説

  • return はない。最後に実行された式の結果が戻り値
  • 関数呼び出しからなる式文なら括弧を省略できる
    • n = fact 9 => (n = fact())(9)
    • コレを許すと文法が曖昧になって構文解析が大変なんだそうだ
    • fact -9 もエラー

7.1 BNF

param : IDENTIFIER
params : param { "," param }
param_list : "(" [ params ] ")"
def     : "def" IDENTIFIER param_list block
args    : expr { "," expr }
postfix : "(" [ args ] ")"
primary : ( "(" expr ")" | NUBMER | IDENTIFIER | STRING ) { postfix }
simple  : expr [ args ]
program : [def | statement ] (";" | EOL)

7.1 BNF 解説

  • 引数 param は識別子
  • 引数列 params は param をカンマで区切ってならべたもの
  • param_list は 仮引数列を括弧で囲んだものか、からの括弧
  • 関数を定義する def
    • ここまで関数定義に使うもの
  • 引数 args は expr をカンマで区切ってならべたもの
  • postfix は実引数 args を括弧で囲んだもの
  • 非終端記号 primary は 末尾に postfix があっても良いことに
  • simple も 関数呼び出し一つに対応するするように、 args があっても良いことに
  • fact(9) は primary 一つ => つまり expr 一つなので simple 1つでもある => つまり statement である

7.1 実装 リスト7.2

  • 上記文法を満たすように構文解析器をの実装を変更します
  • [ args ] の実現に maybe を使ってたり
    • maybe だと、省略されたことを示す部分木(根の節だけをもつ)を挿入
  • 継承先で文法を追加する
    • フィールドsimpleに siple.option(args);
    • program.insertChoice();

7.2 スコープとイクステント

  • 関数内での環境(スコープ)
    • 関数内では新しい環境をつくる
    • 関数の外の環境も参照する必要がある
    • Environment outer を持つ NestedEnv class を定義
    • 変数を探す時は、自分自身を探して、無かったら outer を探す => 繰り返す

7.3 関数を実行する

  • AST の 関数部分木に eval が必要
    • FuncEvaluator リバイザで改訂
    • @Require は先に他のリバイザを適応する

7.3 リバイザの内容

  • DefStmnt クラスに eval を追加
  • PrimaryExpr に関数呼び出しのmethod を追加
    • operand() 関数名(or 変数名)を返す
    • postfix() 仮引き数列を返す
    • eval() 仮引き数列に対して、operandで得た関数を適応する
      • なんか逆な感じですね
      • クロージャーを実装することを見越して、ネストに対応してる
      • これが実態です Arguments オブジェクトの eval を参照
      • 引数を計算して、仮引数の値として新しい環境にセットします

7.3 注意

  • 色々複雑なんだけれども
  • 関数を実行するときの新しい環境 NewEnv
  • NewEnv の Outer は定義したところの環境
    • def はトップレベルにしかかけないので現状帯域変数の環境
  • 引数を計算する環境は callerEnv その名の通り呼んでるところの環境
  • 関数の中で関数呼んだり、クロージャーで呼んだりすると、全部バラバラになるよ
  • 呼び出し毎に NewEnv 作るので再帰もうまくいく

7.4 フィボナッチ

7.5クロージャー対応

inc = fun (x) { x + 1}
inc(3) #=> 4
primary : " fun " param_list block | 元のprimaryの定義

7.5 束縛変数と自由変数

def counter (c) {
   fun () {c = c + 1}
}
c1 = counter(0)
c1() #=> 1
c1() #=> ?
c1() #=> ?

7.6クロージャーの実装

  • 文法を Parser に追加 リスト7.14
  • ASTの節を作成 リスト 7.15
  • eval を定義 7.16
    • Function ブジェクトを作って返すだけ
    • 関数の時に準備しちゃってるので、簡単
  • あたらし Evaluator で Interpreter を作る

まとめ的なもの