【実践goパッケージ】文字列から複素数型の値をパースする #golang

## はじめに

Goの標準パッケージが提供する[`strconv`](https://golang.org/pkg/strconv)パッケージって便利ですよね。
[`ParseInt`](https://golang.org/pkg/strconv/#ParseInt)や[`ParseFloat`]((https://golang.org/pkg/strconv/#ParseFloat))、[`ParseBool`](https://golang.org/pkg/strconv/#ParseBool)を使えば、文字列で表された各型の値をパースし、それぞれの型の値に変換してくれます。

しかし、よく見ると複素数を表す`complex64`型や`complex128`型をパースする関数がありません。
`10i`や`1 + 10i`などをうまくパースする方法はないでしょうか？

そこでこの記事では、「[簡単な式の評価機を作ってみる](http://qiita.com/tenntenn/items/a312d2c5381e36cf4cd3)」で扱った式をパースする方法を基に、文字列から複素数型の値をパースする方法について考えてみます。

## ParseComplexを作る

まずは、作成する関数のシグニチャを考えてみましょう。
`strconv`パッケージにある他の`Parse`系の関数と合わせてみると以下のようになります。

```go
func ParseComplex(s string, bitSize int) (complex128, error)
```

パース元の文字列`s`と`complex64`または`complex128`に変換するか判断するために使う`bitSize`を渡し、`complex128`の値とエラーを返します。

細かい実装は後で考えることにして、まずは`ParseComplex`関数を書いてみましょう。

```go
func ParseComplex(s string, bitSize int) (complex128, error) {
	expr, err := parser.ParseExpr(s)
	if err != nil {
		return 0, err
	}

	p := &complexParser{
		expr:    expr,
		bitSize: bitSize,
	}

	c, err := p.parse()

	if err != nil {
		return 0, err
	}

	return c, nil
}
```

関数の中は至ってシンプルです。
まず、[`parser.ParseExpr`](https://golang.org/pkg/go/parser/#ParseExpr)を使って式をパースして`ast.Expr`を取得しています。
そして、生成した`expr`と`bitSize`を基に、`complexParser`という構造体を作り、`parse`メソッドを呼んでいます。

`go/ast`パッケージを使って、ASTのノードをトラバースしたり、変更するコードは再帰を使うことが多いです。
そのため、`bitSize`などを関数の引数に渡して持ち回しても良いですが、ここは素直に構造体を作ると分かりやすいでしょう。

ここでは利便性を考えて、パッケージ外に公開するのは`ParseComplex`関数だけにしています。
しかし、パーサー自体の挙動をユーザが変えるようにしたい場合は、`Parser`構造体などを作ってフィールドの値を変えることで、パーサーの挙動を変えられるようにすると良いでしょう。
その際にも具体的な実装は公開する必要はないでしょうし、私の場合はよく公開しない構造体（ここでいうと`complexParser`）を作り、公開する構造体のメソッドから呼び出すようにしています。

また、フィールドを変更することで、メソッドのく挙動を変えられるようにしたとしても、デフォルトの挙動を行う関数（ここでいうと`ParseComplex`)のような関数は用意しておくとユーザによって便利だと思います。

## パーサーを作る

さて、前置きが長くなりましたが、`complexParser`を作っていきたいと思います。
この構造体の定義は以下のようになります。

```go
type complexParser struct {
	expr    ast.Expr
	bitSize int
}
```

`expr`と`bitSize`をフィールドに取ります。
正直`expr`はフィールドにしなくても対して問題にはなりません。

さて、`ParseComplex`関数から呼んでいた`parse`メソッドから作っていきましょう。
私の場合、大きな単位をざっと作り、細かな実装は後回しにすることが多いです。
こうすることで、細かい部分を作り込みすぎて、途中で実装方針が変わった場合に変更コストがかかるのを防いでくれますし、頭の中のリソースをそんなに使わなくても実装していけるのでオススメです。
もちろん、方針がガッチリ決まった段階で、一気に細かい部分を深さ優先で作り込むこともありますが。

横道にそれましたが、`parse`メソッドは以下のようになりました。
まず`expr`フィールドの`ast.Expr`型から[`constant.Value`](https://golang.org/pkg/go/constant/#Value)型にパースする`parseExpr`メソッドを呼び出し、`constant.Value`型を取得しています。
そして、`constant.Value`型の値を`complex128`に変換する`complexVal`を呼び出し、複素数型へと変換しています。

```go
func (p *complexParser) parse() (complex128, error) {
	v, err := p.parseExpr(p.expr)
	if err != nil {
		return 0, err
	}

	if v.Kind() != constant.Complex {
		return 0, errors.New("cannot parse")
	}

	return p.complexVal(v)
}
```

次に`complexVal`メソッドをみておきましょう。
`go/constant`パッケージの`constant.Value`型では、複素数型を表すことができますが、`constant.Value`型から直接`complex128`型や`complex64`型の値を取得する方法が提供されていません。
一方で、[`constant.Real`](https://golang.org/pkg/go/constant/#Real)と[`constant.Imag`](https://golang.org/pkg/go/constant/#Imag)を使って、実数部と虚数部を別々に取り出してやります。
また、取り出した実数部や虚数部は、ビット数によって`float32`型か`float64`型に変換する必要があります。
なお、`complex64`型の場合、実数部と虚数部は共に`float32`型の値である必要があり、`complex128`型では、それぞれ`float64`型の値である必要があります。

```go
func (p *complexParser) complexVal(v constant.Value) (complex128, error) {
	rv := constant.Real(v)
	iv := constant.Imag(v)

	// complex64
	if p.bitSize == 64 {
		r, ok := constant.Float32Val(rv)
		if !ok {
			return 0, errors.New("cannot parse real part")
		}
		i, ok := constant.Float32Val(iv)
		if !ok {
			return 0, errors.New("cannot parse imag part")
		}

		return complex128(complex(r, i)), nil
	}

	// complex128
	r, ok := constant.Float64Val(rv)
	if !ok {
		return 0, errors.New("cannot parse real part")
	}
	i, ok := constant.Float64Val(iv)
	if !ok {
		return 0, errors.New("cannot parse imag part")
	}

	return complex(r, i), nil
}
```

続いて、`parseExpr`メソッドをみていきましょう。
「[簡単な式の評価機を作ってみる](http://qiita.com/tenntenn/items/a312d2c5381e36cf4cd3)」でも出てきた、ノードの種類によって型スイッチで処理を分岐する方法です。
`defer`でパニックをリカバーしているのは、`go/constant`パッケージの関数がパニックをする可能性があるからです。

```go
func (p *complexParser) parseExpr(expr ast.Expr) (rv constant.Value, rerr error) {
	defer func() {
		if r := recover(); r != nil {
			switch err := r.(type) {
			case error:
				rv, rerr = constant.MakeUnknown(), err
			default:
				rv, rerr = constant.MakeUnknown(), fmt.Errorf("%v", err)
			}
		}
	}()

	switch expr := expr.(type) {
	case *ast.UnaryExpr:
		return p.parseUnaryExpr(expr)
	case *ast.BinaryExpr:
		return p.parseBinaryExpr(expr)
	case *ast.BasicLit:
		return p.parseBasicLit(expr), nil
	}

	return constant.MakeUnknown(), errors.New("cannot parse")
}
```

さて、ここでは以下の3種類のノードについてパースしています。

* `*ast.UnaryExp`（単項演算式）
* `*ast.BinaryExpr`（2項演算式）
* `*ast.BasicLit`（リテラル）

単項演算式にあたるのは、`-1`などで、2項演算式にあたるのは`1+2i`など、リテラルにあたるのは、`1`や`2i`などです。
この3種類をパースすれば複素数をパースすることができます。

まずは、`parseUnaryExpr`メソッドからみてみましょう。

```go
func (p *complexParser) parseUnaryExpr(expr *ast.UnaryExpr) (constant.Value, error) {
	x, err := p.parseExpr(expr.X)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	return constant.UnaryOp(expr.Op, x, 0), nil
}
````

`parseExpr`メソッドを使って、`constant.Value`に変換し、[`constant.UnaryOp`](https://golang.org/pkg/go/constant/#UnaryOp)関数によって単項演算を行っています。

続いて、`parseBinaryExpr`メソッドをみてみましょう。

```go
func (p *complexParser) parseBinaryExpr(expr *ast.BinaryExpr) (constant.Value, error) {
	x, err := p.parseExpr(expr.X)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	y, err := p.parseExpr(expr.Y)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	return constant.BinaryOp(x, expr.Op, y), nil
}
```

`parseUnaryExpr`とほとんど同じで、オペランドを`parseExpr`メソッドによって、`constant.Value`型にパースし、[`constant.BinaryOp`](https://golang.org/pkg/go/constant/#BinaryOp)関数によって2項演算を行います。

最後に、`parseBasicLit`メソッドをみてみましょう。

```go
func (p *complexParser) parseBasicLit(expr *ast.BasicLit) constant.Value {
	return constant.MakeFromLiteral(expr.Value, expr.Kind, 0)
}
```
こちらは、単純に[`constant.MakeFromLiteral`](https://golang.org/pkg/go/constant/#MakeFromLiteral)を使って、リテラルから`constant.Value`型に変換しているだけです。

これで文字列を複素数型に変換するプログラムを書くことができました。
最後に、すべてのコードを載せておきたいと思います。

```go
package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/constant"
	"go/parser"
)

type complexParser struct {
	expr    ast.Expr
	bitSize int
}

func (p *complexParser) parse() (complex128, error) {
	v, err := p.parseExpr(p.expr)
	if err != nil {
		return 0, err
	}

	if v.Kind() != constant.Complex {
		return 0, errors.New("cannot parse")
	}

	return p.complexVal(v)
}

func (p *complexParser) complexVal(v constant.Value) (complex128, error) {
	rv := constant.Real(v)
	iv := constant.Imag(v)

	// complex64
	if p.bitSize == 64 {
		r, ok := constant.Float32Val(rv)
		if !ok {
			return 0, errors.New("cannot parse real part")
		}
		i, ok := constant.Float32Val(iv)
		if !ok {
			return 0, errors.New("cannot parse imag part")
		}

		return complex128(complex(r, i)), nil
	}

	// complex128
	r, ok := constant.Float64Val(rv)
	if !ok {
		return 0, errors.New("cannot parse real part")
	}
	i, ok := constant.Float64Val(iv)
	if !ok {
		return 0, errors.New("cannot parse imag part")
	}

	return complex(r, i), nil
}

func (p *complexParser) parseExpr(expr ast.Expr) (rv constant.Value, rerr error) {
	defer func() {
		if r := recover(); r != nil {
			switch err := r.(type) {
			case error:
				rv, rerr = constant.MakeUnknown(), err
			default:
				rv, rerr = constant.MakeUnknown(), fmt.Errorf("%v", err)
			}
		}
	}()

	switch expr := expr.(type) {
	case *ast.UnaryExpr:
		return p.parseUnaryExpr(expr)
	case *ast.BinaryExpr:
		return p.parseBinaryExpr(expr)
	case *ast.BasicLit:
		return p.parseBasicLit(expr), nil
	}

	return constant.MakeUnknown(), errors.New("cannot parse")
}

func (p *complexParser) parseUnaryExpr(expr *ast.UnaryExpr) (constant.Value, error) {
	x, err := p.parseExpr(expr.X)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	return constant.UnaryOp(expr.Op, x, 0), nil
}

func (p *complexParser) parseBinaryExpr(expr *ast.BinaryExpr) (constant.Value, error) {
	x, err := p.parseExpr(expr.X)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	y, err := p.parseExpr(expr.Y)
	if err != nil {
		return constant.MakeUnknown(), err
	}

	return constant.BinaryOp(x, expr.Op, y), nil
}

func (p *complexParser) parseBasicLit(expr *ast.BasicLit) constant.Value {
	return constant.MakeFromLiteral(expr.Value, expr.Kind, 0)
}

func ParseComplex(s string, bitSize int) (complex128, error) {
	expr, err := parser.ParseExpr(s)
	if err != nil {
		return 0, err
	}

	p := &complexParser{
		expr:    expr,
		bitSize: bitSize,
	}

	c, err := p.parse()

	if err != nil {
		return 0, err
	}

	return c, nil
}

func main() {
	fmt.Println(ParseComplex("1 + 10i", 128))
}
```

上記のコードは、[The Go Playground](https://play.golang.org/p/_HHK7abxXi)でも動かすことができます。

## おわりに

この記事では、実践的な`go`パッケージの使い方として、文字列から複素数型をパースするプログラムについて解説しました。
ぜひ、みなさんも`go`パッケージを使ってライブラリや開発ルールを作ってみてください。