フィボナッチ数列計算のためだけに作った言語、FibLang

# フィボナッチ数列計算のためだけに作った言語、FibLang

「[FizzBuzzを書くためだけに作った言語、FizzBuzzLang](https://qiita.com/yhirose/items/c69a70d50668d97977af)」の第2弾です。

フィボナッチ数列計算以外のことは何もできません。:cry:

今回も前回と同じ6つのステップで行います。

  1. 頭の中のオレオレ言語で「フィボナッチ数列計算プログラム」を書く
  1. 文法をPEG (Parsing Expression Grammar) に落とす
  1. AST (抽象構文木) を出力するパーサーの作成
  1. 値
  1. 環境
  1. 評価器

## 1. 頭の中のオレオレ言語でフィボナッチ数列計算プログラムを書く

```cpp
def fib(x)
  x < 2 ? 1 : fib(x - 2) + fib(x - 1)

for n from 1 to 30
  puts(fib(n))
```

FizzBuzLangとほぼ同じですが、以下の違いがあります。

  * 関数定義: `def`
  * 新たな演算子: `+`, `-`, `<`
  * 必要ない演算子: `%`, `==`

## 2. 文法をPEGに落とす

今回も「[PEG Playground](https://yhirose.github.io/cpp-peglib/)」上で試行錯誤しながら、PEG文法を作成する。

完成したPEG文法はこちら。

```
# Syntax Rules
STATEMENTS              <- (DEFINITION / EXPRESSION)*
DEFINITION              <- 'def' Identifier '(' Identifier ')' EXPRESSION
EXPRESSION              <- TERNARY
TERNARY                 <- CONDITION ('?' EXPRESSION ':' EXPRESSION)?
CONDITION               <- INFIX (ConditionOperator INFIX)?
INFIX                   <- CALL (InfixOperator CALL)*
CALL                    <- PRIMARY ('(' EXPRESSION ')')?
PRIMARY                 <- FOR / Identifier / '(' EXPRESSION ')' / Number
FOR                     <- 'for' Identifier 'from' Number 'to' Number EXPRESSION

# Token Rules
ConditionOperator       <- '<'
InfixOperator           <- '+' / '-'
Identifier              <- !Keyword < [a-zA-Z][a-zA-Z0-9_]* >
Number                  <- < [0-9]+ >

Keyword                 <- 'def' / 'for' / 'from' / 'to'
%whitespace             <- [ \t\r\n]*
```

## 3. AST (抽象構文木) を出力するパーサーの作成

これは前回と全く同じ。

## 4. 値

今回は`String`型は必要ないので削除。`Function`型は、ユーザーが関数定義できるようになるために改良。

FizzBuzzLangの時の`Function`

```cpp
using Function = function<Value(const Value&)>;
```

FibLangの`Function`では`struct`にし、引数(`param`)と関数本体(`eval`)を保存できるようにした。

```cpp
struct Function {
  string param;
  function<Value(shared_ptr<Environment> env)> eval;

  Function(const string& param,
           function<Value(shared_ptr<Environment> env)>&& eval)
      : param(param), eval(eval) {}
};
```

さらに`==`演算子を削除し、`<`演算子を追加する。

```cpp
  // Comparison
  bool operator<(const Value& rhs) const {
    switch (type) {
      case Type::Nil:
        return false;
      case Type::Bool:
        return to_bool() < rhs.to_bool();
      case Type::Long:
        return to_long() < rhs.to_long();
      default:
        throw logic_error("invalid internal condition.");
    }
    // NOTREACHED
  }
```

## 5. 環境

`Environment`は特に変更はなし。

## 6. 評価器

`STATEMENTS`と`DEFINITION`ルール用の評価器を追加。

```cpp
Value eval(const Ast& ast, shared_ptr<Environment> env) {
  switch (ast.tag) {
    // Rules
    case "STATEMENTS"_:
      return eval_statements(ast, env);
    case "DEFINITION"_:
      return eval_definition(ast, env);
    case "TERNARY"_:
```

```cpp
Value eval_statements(const Ast& ast, shared_ptr<Environment> env) {
  if (!ast.nodes.empty()) {
    auto it = ast.nodes.begin();
    while (it != ast.nodes.end() - 1) {
      eval(**it, env);
      ++it;
    }
    return eval(**it, env);
  }
  return Value();
}

Value eval_definition(const Ast& ast, shared_ptr<Environment> env) {
  const auto& name = ast.nodes[0]->token;
  const auto& param = ast.nodes[1]->token;
  auto body = ast.nodes[2];

  env->set_value(name,
                 Value(Function(param, [=](shared_ptr<Environment> callEnv) {
                   return eval(*body, callEnv);
                 })));

  return Value();
}
```

`MULTIPLICATIVE`は`INFIX`と名前を変更し、`%`ではなく`+`と`-`を扱えるようになった。それに伴い`eval_multiplicative`を`eval_infix`にリネームし、処理内容を修正。

```cpp
Value eval_infix(const Ast& ast, shared_ptr<Environment> env) {
  auto l = eval(*ast.nodes[0], env).to_long();
  for (auto i = 1; i < ast.nodes.size(); i += 2) {
    auto o = ast.nodes[i]->token;
    auto r = eval(*ast.nodes[i + 1], env).to_long();
    if (o == "+") {
      l += r;
    } else if (o == "-") {
      l -= r;
    }
  }
  return Value(l);
}
```

`eval_condition`は`<`を使うように修正。

```cpp
Value eval_condition(const Ast& ast, shared_ptr<Environment> env) {
  auto lhs = eval(*ast.nodes[0], env);
  auto rhs = eval(*ast.nodes[2], env);
  auto ret = lhs < rhs;
  return Value(ret);
}
```

`eval_call`は引数を扱えるように修正。

```cpp
Value eval_call(const Ast& ast, shared_ptr<Environment> env) {
  auto name = ast.nodes[0]->token;
  auto fn = env->get_value(name).to_function();
  auto val = eval(*ast.nodes[1], env);

  auto callEnv = make_shared<Environment>(env);
  callEnv->set_value(fn.param, move(val));

  try {
    return fn.eval(callEnv);
  } catch (const Value& e) {
    return e;
  }
}
```

最後にビルトイン関数`puts`を新しい`Function`型に合わせて修正。

```cpp
  env->set_value("puts", Value(Function("arg", [](shared_ptr<Environment> env) {
                   cout << env->get_value("arg").str() << endl;
                   return Value();
                 })));
```

コンパイルして実行すると、

```
> time ./fib fib.fib
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269

real	1m8.487s
user	1m8.177s
sys	0m0.103s
```

おっ、遅すぎる。。。 ^^;

(2020/7/21 追記）`-O2`で再度コンパイルして実行したところ、実行時間は以下のように改善されました。

```
real	0m10.811s
user	0m10.768s
sys	0m0.016s
```

350行に満たないコード量でFibLangインタープリタを完成したものの、構文木駆動型インタープリタでは遅すぎることが判明。次回はバックエンドにLLVMを用いて高速化してみよう。

<details><summary>最終的なソースコード</summary><div>

```cpp:fib.cc
//
//  FibLang
//  A Programming Language just for writing Fibonacci number program. :)
//
//  Copyright (c) 2020 Yuji Hirose. All rights reserved.
//  MIT License
//

#include <fstream>

#include "peglib.h"

using namespace std;
using namespace peg;
using namespace peg::udl;

bool read_file(const char* path, vector<char>& buf) {
  ifstream ifs(path, ios::in);
  if (ifs.fail()) {
    return false;
  }
  buf.resize(static_cast<unsigned int>(ifs.seekg(0, ios::end).tellg()));
  ifs.seekg(0, ios::beg).read(&buf[0], static_cast<streamsize>(buf.size()));
  return true;
}

//-----------------------------------------------------------------------------
// Parser
//-----------------------------------------------------------------------------

shared_ptr<Ast> parse(const vector<char>& source, ostream& out) {
  parser parser(R"(
    # Syntax Rules
    STATEMENTS              <- (DEFINITION / EXPRESSION)*
    DEFINITION              <- 'def' Identifier '(' Identifier ')' EXPRESSION
    EXPRESSION              <- TERNARY
    TERNARY                 <- CONDITION ('?' EXPRESSION ':' EXPRESSION)?
    CONDITION               <- INFIX (ConditionOperator INFIX)?
    INFIX                   <- CALL (InfixOperator CALL)*
    CALL                    <- PRIMARY ('(' EXPRESSION ')')?
    PRIMARY                 <- FOR / Identifier / '(' EXPRESSION ')' / Number
    FOR                     <- 'for' Identifier 'from' Number 'to' Number EXPRESSION

    # Token Rules
    ConditionOperator       <- '<'
    InfixOperator           <- '+' / '-'
    Identifier              <- !Keyword < [a-zA-Z][a-zA-Z0-9_]* >
    Number                  <- < [0-9]+ >
    Keyword                 <- 'def' / 'for' / 'from' / 'to'

    %whitespace             <- [ \t\r\n]*
  )");

  parser.enable_ast();

  parser.log = [&](size_t ln, size_t col, const string& msg) {
    out << ln << ":" << col << ": " << msg << endl;
  };

  shared_ptr<Ast> ast;
  if (parser.parse_n(source.data(), source.size(), ast)) {
    return AstOptimizer(true).optimize(ast);
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
// Value
//-----------------------------------------------------------------------------

struct Value;
struct Environment;

struct Function {
  string param;
  function<Value(shared_ptr<Environment> env)> eval;

  Function(const string& param,
           function<Value(shared_ptr<Environment> env)>&& eval)
      : param(param), eval(eval) {}
};

struct Value {
  enum class Type { Nil, Bool, Long, Function };
  Type type;
  any v;

  // Constructor
  Value() : type(Type::Nil) {}
  explicit Value(bool b) : type(Type::Bool), v(b) {}
  explicit Value(long l) : type(Type::Long), v(l) {}
  explicit Value(Function&& f) : type(Type::Function), v(f) {}

  // Cast value
  bool to_bool() const {
    switch (type) {
      case Type::Bool:
        return any_cast<bool>(v);
      case Type::Long:
        return any_cast<long>(v) != 0;
      default:
        throw runtime_error("type error.");
    }
  }

  long to_long() const {
    switch (type) {
      case Type::Long:
        return any_cast<long>(v);
      default:
        throw runtime_error("type error.");
    }
  }

  Function to_function() const {
    switch (type) {
      case Type::Function:
        return any_cast<Function>(v);
      default:
        throw runtime_error("type error.");
    }
  }

  // Comparison
  bool operator<(const Value& rhs) const {
    switch (type) {
      case Type::Nil:
        return false;
      case Type::Bool:
        return to_bool() < rhs.to_bool();
      case Type::Long:
        return to_long() < rhs.to_long();
      default:
        throw logic_error("invalid internal condition.");
    }
    // NOTREACHED
  }

  // String representation
  string str() const {
    switch (type) {
      case Type::Nil:
        return "nil";
      case Type::Bool:
        return to_bool() ? "true" : "false";
      case Type::Long:
        return std::to_string(to_long());
      case Type::Function:
        return "[function]";
      default:
        throw logic_error("invalid internal condition.");
    }
  }
};

//-----------------------------------------------------------------------------
// Environment
//-----------------------------------------------------------------------------

struct Environment {
  shared_ptr<Environment> outer;
  map<string, Value> values;

  Environment(shared_ptr<Environment> outer = nullptr) : outer(outer) {}

  const Value& get_value(const string& s) const {
    if (values.find(s) != values.end()) {
      return values.at(s);
    } else if (outer) {
      return outer->get_value(s);
    }
    throw runtime_error("undefined variable '" + s + "'...");
  }

  void set_value(const string& s, Value&& val) { values.emplace(s, val); }
};

//-----------------------------------------------------------------------------
// Interpreter
//-----------------------------------------------------------------------------

Value eval(const Ast& ast, shared_ptr<Environment> env);

Value eval_statements(const Ast& ast, shared_ptr<Environment> env) {
  if (!ast.nodes.empty()) {
    auto it = ast.nodes.begin();
    while (it != ast.nodes.end() - 1) {
      eval(**it, env);
      ++it;
    }
    return eval(**it, env);
  }
  return Value();
}

Value eval_definition(const Ast& ast, shared_ptr<Environment> env) {
  const auto& name = ast.nodes[0]->token;
  const auto& param = ast.nodes[1]->token;
  auto body = ast.nodes[2];

  env->set_value(name,
                 Value(Function(param, [=](shared_ptr<Environment> callEnv) {
                   return eval(*body, callEnv);
                 })));

  return Value();
}

Value eval_ternary(const Ast& ast, shared_ptr<Environment> env) {
  auto cond = eval(*ast.nodes[0], env).to_bool();
  auto idx = cond ? 1 : 2;
  return eval(*ast.nodes[idx], env);
}

Value eval_condition(const Ast& ast, shared_ptr<Environment> env) {
  auto lhs = eval(*ast.nodes[0], env);
  auto rhs = eval(*ast.nodes[2], env);
  auto ret = lhs < rhs;
  return Value(ret);
}

Value eval_infix(const Ast& ast, shared_ptr<Environment> env) {
  auto l = eval(*ast.nodes[0], env).to_long();
  for (auto i = 1; i < ast.nodes.size(); i += 2) {
    auto o = ast.nodes[i]->token;
    auto r = eval(*ast.nodes[i + 1], env).to_long();
    if (o == "+") {
      l += r;
    } else if (o == "-") {
      l -= r;
    }
  }
  return Value(l);
}

Value eval_call(const Ast& ast, shared_ptr<Environment> env) {
  auto name = ast.nodes[0]->token;
  auto fn = env->get_value(name).to_function();
  auto val = eval(*ast.nodes[1], env);

  auto callEnv = make_shared<Environment>(env);
  callEnv->set_value(fn.param, move(val));

  try {
    return fn.eval(callEnv);
  } catch (const Value& e) {
    return e;
  }
}

Value eval_for(const Ast& ast, shared_ptr<Environment> env) {
  auto ident = ast.nodes[0]->token;
  auto from = eval(*ast.nodes[1], env).to_long();
  auto to = eval(*ast.nodes[2], env).to_long();
  auto& expr = *ast.nodes[3];

  for (auto i = from; i <= to; i++) {
    auto call_env = make_shared<Environment>(env);
    call_env->set_value(ident, Value(i));
    eval(expr, call_env);
  }

  return Value();
}

Value eval(const Ast& ast, shared_ptr<Environment> env) {
  switch (ast.tag) {
    // Rules
    case "STATEMENTS"_:
      return eval_statements(ast, env);
    case "DEFINITION"_:
      return eval_definition(ast, env);
    case "TERNARY"_:
      return eval_ternary(ast, env);
    case "CONDITION"_:
      return eval_condition(ast, env);
    case "INFIX"_:
      return eval_infix(ast, env);
    case "CALL"_:
      return eval_call(ast, env);
    case "FOR"_:
      return eval_for(ast, env);

    // Tokens
    case "Identifier"_:
      return Value(env->get_value(ast.token));
    case "Number"_:
      return Value(stol(ast.token));
  }

  return Value();
}

Value interpret(const Ast& ast) {
  auto env = make_shared<Environment>();

  env->set_value("puts", Value(Function("arg", [](shared_ptr<Environment> env) {
                   cout << env->get_value("arg").str() << endl;
                   return Value();
                 })));

  return eval(ast, env);
}

//-----------------------------------------------------------------------------
// main
//-----------------------------------------------------------------------------

int main(int argc, const char** argv) {
  if (argc < 2) {
    cerr << "usage: fib [source file path]" << endl;
    return 1;
  }

  auto path = argv[1];

  vector<char> source;
  if (!read_file(path, source)) {
    cerr << "can't open the source file." << endl;
    return 2;
  }

  auto ast = parse(source, cerr);
  if (!ast) {
    return 3;
  }

  try {
    interpret(*ast);
  } catch (const exception& e) {
    cerr << e.what() << endl;
    return 4;
  }

  return 0;
}
```

</div>