Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る（gojaのちょっとした改造)

# 概要

このシリーズの最後の投稿です。
JavaScriptエンジンである`goja`は仕様が**ECMAScript 5.1**なので`Proxy`などはありません。そこで未定義のプロパティの参照があったらオブジェクトの特定のメソッドを呼び出すように改造します。
用途は「[Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る](https://qiita.com/h6591/items/6dec513868360becd02e)」の[Appendix のsqlite3の例](https://qiita.com/h6591/items/6dec513868360becd02e#sqlite3%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BE%8B)の最後の方に書きました。

# gojaの改造

## 改造しないときのデータベースアクセスの例

データベースアクセスの疑似オブジェクトの例
次のようなクラスを`db.js`として作成します。

```javascript:db.js
DB=function(name) {
    this.name = name
    //Go言語のsql.Openを呼び出し、*sql.DBをラップしたオブジェクトを返す
}

DB.prototype.exec=function(sql) {
    //sql.DB.Execを実行
    print(sql+"; を実行しました")
}
DB.prototype.query=function(sql) {
    print(sql+"; を実行しました")
    //sql.DB.Queryを実行し
    //*sql.Rowsをラップしたオブジェクトを返す
    return [1,2,3,4]
}

DB.prototype.table = function(name) {
  return new TABLE(this, name)
}

TABLE = function(db, name) {
  this.db = db
  this.name = name
}
TABLE.prototype.findAll = function() {
   return this.db.query("select * from "+this.name)
}
```
実行

```javascript
> load("db.js")
undefined
> db=new DB("database")
{"name":"database"}
> db.exec("create table sample(a integer, b varchar)")
create table sample(a integer, b varchar); を実行しました
undefined
> db.query("select * from sample")
select * from sample; を実行しました
[1,2,3,4]
> s=db.table("sample")
{"db":{"name":"database"},"name":"sample"}
> s.findAll()
select * from sample; を実行しました
[1,2,3,4]
>
```

`s=db.table("sample")`をMongoDBのmongoシェルのように`s=db.sample`または`db.sample.findAll()`のように使いたいが`Proxy`などがないので実現できない。当然、次のようにエラーになります。

```javascript
> db.sample.findAll()
TypeError: Cannot read property 'findAll' of undefined at console:1:1(3)
>
```
そこで、gojaを改造します。

## gojaのobject.goを改造

`object.go`に`func getPropStr()`があります。現在のソースだと`120行目`です。次のようなコードです。

```go:object.go
func (o *baseObject) getPropStr(name string) Value {
	if val := o.getOwnProp(name); val != nil {
		return val
	}
	if o.prototype != nil {
		return o.prototype.self.getPropStr(name)
	}
	return nil
}
```

これを次のように改造します。未定義のプロパティを参照したとき`__missing__`が定義されていたら、未定義のプロパティ名を引数にして呼び出します。

```go:object.go
func (o *baseObject) getPropStr(name string) Value {
	if val := o.getOwnProp(name); val != nil {
		return val
	}
	if o.prototype == nil {
		return nil
	}
	if val := o.prototype.self.getPropStr(name); val != nil {
		return val
	}
	getProp := o.getOwnProp("__missing__")
	if getProp == nil {
		return nil
	}
	if getFunc, ok := AssertFunction(getProp); ok {
		nameVal := o.val.runtime.ToValue(name)
		if ret, err := getFunc(o.val, nameVal); err == nil {
			return ret
		} else {
		    o.val.runtime.Interrupt(err)
		    return nil
		}
	}
	return nil
}
```

`db.js`の`DB`に`__missing__`を追加します

```javascript:db.js
DB=function(name) {
    this.name = name
    //Go言語のsql.Openを呼び出し、*sql.DBをラップしたオブジェクトを返す
    this.__missing__ = function(prop) {
      return this.table(prop)
    }
}
```

実行

```javascript
go run main.go
> load("db.js")
undefined
> db=new DB("database")
{"name":"database"}
> db.sample.findAll()
select * from sample; を実行しました
[1,2,3,4]
> s=db.sample
{"db":{"name":"database"},"name":"sample"}
> s.findAll()
select * from sample; を実行しました
[1,2,3,4]
>
```

考えた通りに動作しました。

# まとめ

ちょっとした改造でオリジナルなJavaScriptエンジンも作れます。頑張れば`Proxy`クラスも作れそうです。