Borgoを使ってRust風のコードからGoのコードを生成する

[Borgo](https://github.com/borgo-lang/borgo) というプログラミング言語を使ってみました。

Borgo は Go のトランスパイラで、JavaScript に対する TypeScript のような位置付けのものですが、Go の構文拡張ではなく Rust に似た構文を採用している点が特徴です。

Go の型まわりやエラーハンドリングの不満点を解決するためのものだと思われますが、現時点（2024-05-26 時点）では考慮不足や不備が散見され、まだまだ完成度は低そうでした。

また、あくまでも Rust 風の構文のため、Rust と微妙に違っていたりもします。

# 準備

まず、Borgo のコンパイラを使えるようにします。

現時点でビルド済みバイナリの配布は見当たらなかったため、github からソースコードを取得してビルドしました。

Borgo コンパイラ自体は Rust で実装されており容易にビルドできます。

```sh:ソースコードの取得
$ git clone https://github.com/borgo-lang/borgo
```

```sh:ビルド例
$ cd borgo
$ cargo build
```

ただし、今回は rust-toolchain.toml に記載されている Rust のバージョンが少々古かったので、cargo build 前にこのファイルをリネームして参照しないようにしました。

```sh:今回実施したビルド
$ cd borgo
$ mv rust-toolchain.toml rust-toolchain.toml_bk 
$ cargo build
```

# サンプル作成

ここでは、指定したテキストファイルの各行を以下の3通りに分類する処理を Borgo で実装してみます。

* 空（Empty）
* 数値（Number）
* 文字列（String）

なお、数値への変換に失敗したものを文字列とします。

## Borgo による実装

Borgo で実装するとこのようになりました。

```rust:sample/readfile.brg
use bufio
use fmt
use os
use strconv
use strings

enum Element {
    Empty,
    Number(float64),
    String(string),
}

fn readFile(file: string) -> Result<[Element]> {
    let f = os.Open(file)?
    defer f.Close()

    let sc = bufio.NewScanner(f)

    let mut res = []

    while sc.Scan() {
        let line = sc.Text()

        if strings.NewReader(line).Len() > 0 {
            res = res.Append(match strconv.ParseFloat(line, 64) {
                Ok(n) => Element.Number(n),
                Err(_) => Element.String(line),
            })
        } else {
            res = res.Append(Element.Empty)
        }
    }

    Ok(res)
}

fn printElements(es: [Element]) -> Result<int> {
    for (i, x) in es.Enumerate() {
        match x {
            Element.Empty => fmt.Printf("%d : empty\n", i)?,
            Element.Number(n) => fmt.Printf("%d : number=%f \n", i, n)?,
            Element.String(s) => fmt.Printf("%d : string=%s \n", i, s)?,
        }
    }

    Ok(0)
}

fn main() {
    match readFile(os.Args[1]) {
        Ok(es) => printElements(es),
        Err(e) => fmt.Printf("[error] %v\n", e),
    }
}
```

Go の API を使った Rust っぽいコードでなかなか違和感のある見た目ですが、パターンマッチや Result によるエラーハンドリング（```?``` の利用）を Rust と同じように使える点は便利だと思います。

ただ、Borgo の不備を回避するために工夫した箇所がそれなりにあります。

例えば ```for (i, x) in es.Enumerate()``` の箇所では、```for (_, x) in es.Enumerate()``` にすると Borgo コンパイラが panic で落ちましたし、```for (_i, x) in es.Enumerate()``` にすると（```_i``` 変数を使用していないと）go build に失敗します。

## ビルド

まずは、Borgo コンパイラを使って Go のソースコードへ変換します。

現時点の Borgo コンパイラは引数として ```build``` か ```test``` しか受け取れません。
引数を build にする事でカレントディレクトリ内の .brg ファイルを処理して .go ファイルを生成するようです。

```sh:Borgo ビルド（Go ソースコードの生成）
$ cd sample
$ ../borgo/target/debug/compiler build
```

これでカレントディレクトリ（sample）内に ```core.go``` と ```readfile.go``` ファイルが生成されました。

ただし、readfile.go に ```*current*``` という無駄な import が含まれてしまっており、このままだと go build が通らないので修正（コメントアウト化）する必要がありました。[^1]

[^1]: .brg ファイルの内容次第では ```*current*``` が挿入されない場合もあるようなので Borgo コンパイラの不具合だと思われます

```go: sample/readfile.go の修正例（*current* をコメントアウトする）
package main
import (
 "strconv"
 "fmt"
 // "*current*"
 "bufio"
 "os"
 "strings"
)
...省略
```

sample ディレクトリ内で下記を実行して、生成された Go のソースコードをビルドします。

```sh:Go のビルド
$ go mod init app
$ go build
```

## 動作確認

下記 data.txt に対する実行結果はこのようになります。

```text:sample/data.txt
a1
10

b2
20.3

c3
```

```sh:実行例1
$ ./app data.txt
```
```sh
0 : string=a1 
1 : number=10.000000 
2 : empty
3 : string=b2 
4 : number=20.300000 
5 : empty
6 : string=c3 
```

また、存在しないファイルを指定した場合の実行結果はこのようになります。

```sh:実行例2
$ ./app none.txt
```
```
[error] open none.txt: no such file or directory
```

ちなみに、引数を与えなかった場合は当然ながら panic になります。

```sh:実行例3
$ ./app
```
```
panic: runtime error: index out of range [1] with length 1

...省略
```