『Go言語でつくるインタプリタ』をRustで実装 ①字句解析器

# はじめに
現在，`Rust`の学習のためにインタプリタを実装してる．
参考にしてる本はおなじみの下記文献．

[![picture978-4-87311-822-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183420/1403bd1c-23f2-0241-d88d-54aa2ac0fa3e.gif)](https://www.oreilly.co.jp/books/9784873118222/)
[『Go言語でつくるインタプリタ』](https://www.oreilly.co.jp/books/9784873118222/)
Thorsten Ball著、設樂 洋爾　訳，
オライリー社，2018年06月 発行 

第1章の『字句解析』の字句解析器を無事に実装を終えたので，
`Go`で書かれたサンプルコードを`Rust`に移植する際に行なったことをまとめていこうと思う．
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">字句解析器(Lexer)の実装まで，本文でいうところの第1章まで終わり(<a href="https://t.co/YKrD9wWH1Z">https://t.co/YKrD9wWH1Z</a>)．Goで書かれたサンプルをRustで置き換えててる．次は構文解析器(Parser)の実装．</p>&mdash; Scstechr (@Scstechr) <a href="https://twitter.com/Scstechr/status/1220604423803523072?ref_src=twsrc%5Etfw">January 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

なお，字句解析器の解説についてはぜひ原著を購入して参照してほしい（宣伝）．

#本文
記事執筆時点のレポジトリは[こちら](https://github.com/Scstechr/nlang/tree/1308191c8513dc5a5f2deb41ba3d77fb78c75dea)．

## コーディングルール
原著は`Go`で書かれているので命名規則が`Rust`とは違う．命名規則→[Go](https://medium.com/@lynzt/variable-naming-conventions-in-go-89fe1ef17b0a)，[Rust](https://doc.rust-lang.org/1.0.0/style/style/naming/README.html)
簡単にいうと，`Go`では多くの場合で`camelCase`になるところは`Rust`だと`snake_case`になると思う．
本文中のコードをそのまま移植するとコンパイラから`Warning!`を大量に投げられるので，
一時的に各コードの先頭に下記を追記することにした(のちに削除する予定）．

```rust:コンパイラの警告を無視する
#![allow(non_snake_case)]
#![allow(dead_code)]
#![allow(unused_imports)]
```
`#![allow(unused_imports)]`を入れてるのは，
`use`だけ宣言してまだ実装しない・使用しないなどの事象が多かったためである．
また，デバッグ用に作っただけの関数などは`#[allow(dead_code)]`をつけたりした．

## `Rust`には`byte`型がない
本文中の`type Lexer struct`にはメンバとして現在検査中の文字を表す`ch`を`byte`型で実装してる．
`Rust`には`byte`型はないので，`ch`は`u8`で実装し，残りのメンバも`usize`にするなど工夫をした．

```rust:Lexer
pub struct Lexer {
  input: String,
  position: usize,
  read_position: usize,
  ch: u8
}
```

なんで`i32`でなく`usize`にしたかというと，
`position`/`read_position`は他の箇所で配列のインデックスとして使用するからだ．
必要な箇所では，`u8`から`char`に変換した．
例えば，次の文字を覗き見(peek)する`peek_char()`(本文中では`peekChar()`)の実装は以下のとおり．

```rust:peek_char()の実装
fn peek_char(&self) {
  if self.read_position >= self.input.len() {
    char::from(0)
  } else {
    char::from(self.input.as_bytes()[self.position + 1])
  }
}
```

## `switch`/`case`の代わりの`match`
読み込んだ文字を原著では`switch`/`case`を用いて各`Token`として識別している．
当然`Rust`には`switch`/`case`はないので，代わりに`match`を用いた．

```rust:match
let tok: token::Token = match self.ch {
  b'=' => new_token(token::ASSIGN, self.ch),
  b'+' => new_token(token::PLUS, self.ch),
  ...
  _ => token::Token {
    Type: token::EOF.to_string(),
    Literal: "".to_string(),
  },
};
```

各`Token`は`const ASSIGN: &str = "=";`の形で実装している．

## テストについて
原著ではおそらくHashMapのVector?を用いてテストを書いていた(`Go`なのでわからん)．
さらに，何番目の`Token`でエラー出たという情報を得るために`range`を使っていた．
当初，自分も`enumerate`を使ったり`std::collections::HashMap`を使っていたが，
最終的に以下のように落ち着いた．

```rust:test
fn lexer_simple_test() {
   use crate::lexer;
   use crate::token;

   let input = "=+(){},;";
   let tests = vec![
       lexer::new_token(token::ASSIGN, "=".as_bytes()),
       lexer::new_token(token::PLUS, "+".as_bytes()),
       lexer::new_token(token::LPAREN, "(".as_bytes()),
       lexer::new_token(token::RPAREN, ")".as_bytes()),
       lexer::new_token(token::LBRACE, "{".as_bytes()),
       lexer::new_token(token::RBRACE, "}".as_bytes()),
       lexer::new_token(token::COMMA, ",".as_bytes()),
       lexer::new_token(token::SEMICOLON, ";".as_bytes()),
       lexer::new_token(token::EOF, "".as_bytes()),
   ];
   let mut l = lexer::new(input);
   for t in tests.iter() {
       let tok = l.next_token();
       assert_eq!(tok.Type, t.Type);
       assert_eq!(tok.Literal, t.Literal);
   }
}
```

詳しい情報が欲しい場合は以下を`for`ループ内に記述する．

```rust:test
println!(
   "tok: [{:#?}:{:#?}]\x1b[30Gt: [{:#?}:{:#?}]",
   tok.Type, tok.Literal, t.Type, t.Literal
);
```

## キーワードマッチに`Map`を使う
原著ではおしゃれに`map`を使用する方法が紹介されていた．
筆者はまだ`Rust`の`std::iter::Map`を使いこなせてないのでおとなしく`match`で対応した．

```rust:matchを使う
let literal = self.read_identifier();
let t = match literal.as_str() {
    "fn" => token::FUNCTION.to_string(),
    "let" => token::LET.to_string(),
    ...
    _ => token::ID.to_string(),
};
```
この部分もゆくゆくは`map`で置き換えたい．

# おわり
冒頭のレポジトリを`clone`して`cargo build`してもらえれば動く字句解析器が手に入る．
`make`すれば`REPL`(Read-Eval-Print-Loop)ではなく`RPL`(Read-Print-Loop)が実行される．
まだコードの量も少ないし，字句解析器単体の実装を追いたい場合は有用かもしれない．

願わくばモチベがこのまま持って完走したい．