1
1

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

Posted at

Borgo というプログラミング言語を使ってみました。

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

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

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

準備

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

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

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

ソースコードの取得
$ git clone https://github.com/borgo-lang/borgo
ビルド例
$ cd borgo
$ cargo build

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

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

サンプル作成

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

  • 空(Empty)
  • 数値(Number)
  • 文字列(String)

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

Borgo による実装

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

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 コンパイラは引数として buildtest しか受け取れません。
引数を build にする事でカレントディレクトリ内の .brg ファイルを処理して .go ファイルを生成するようです。

Borgo ビルド(Go ソースコードの生成)
$ cd sample
$ ../borgo/target/debug/compiler build

これでカレントディレクトリ(sample)内に core.goreadfile.go ファイルが生成されました。

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

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

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

Go のビルド
$ go mod init app
$ go build

動作確認

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

sample/data.txt
a1
10

b2
20.3

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

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

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

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

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

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1