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 で実装するとこのようになりました。
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 ファイルを生成するようです。
$ cd sample
$ ../borgo/target/debug/compiler build
これでカレントディレクトリ(sample)内に core.go
と readfile.go
ファイルが生成されました。
ただし、readfile.go に *current*
という無駄な import が含まれてしまっており、このままだと go build が通らないので修正(コメントアウト化)する必要がありました。1
package main
import (
"strconv"
"fmt"
// "*current*"
"bufio"
"os"
"strings"
)
...省略
sample ディレクトリ内で下記を実行して、生成された Go のソースコードをビルドします。
$ go mod init app
$ go build
動作確認
下記 data.txt に対する実行結果はこのようになります。
a1
10
b2
20.3
c3
$ ./app data.txt
0 : string=a1
1 : number=10.000000
2 : empty
3 : string=b2
4 : number=20.300000
5 : empty
6 : string=c3
また、存在しないファイルを指定した場合の実行結果はこのようになります。
$ ./app none.txt
[error] open none.txt: no such file or directory
ちなみに、引数を与えなかった場合は当然ながら panic になります。
$ ./app
panic: runtime error: index out of range [1] with length 1
...省略
-
.brg ファイルの内容次第では
*current*
が挿入されない場合もあるようなので Borgo コンパイラの不具合だと思われます ↩