このガイドについて

本ドキュメントはErgプロジェクトの開発に関するガイドです。 コントリビューションの方法、開発に関する指針、開発環境のセットアップ方法などを説明しています。

コンパイラの設計・実装に関する資料はこちらです。

ブランチの命名と運用方針

badge

  • 基本的に開発はmainブランチ一本で行う(トランクベース開発)。どうしてもブランチを切らないと作業しにくい場合のみfeature-*ブランチかissue-*ブランチを作成する。

main

  • メイン開発ブランチ

  • 以下の条件を満たす必要がある

  • コンパイルが成功する

beta (現在のところは作らない)

  • 最新のベータリリース

  • 以下の条件を満たす必要がある

  • コンパイルが成功する

  • 全てのテストが成功する

feature-*

  • 特定の一機能を開発するブランチ

  • mainを切って作る

  • 条件なし

issue-*

  • 特定のissueを解決するブランチ

  • 条件なし

fix-*

  • 特定のバグを解決するブランチ(issueがバグの場合に、issue-*の代わりに作成する)

  • 条件なし

erg build features

badge

debug

デバッグモードにする。これにより、Erg内部での挙動が逐次ログ表示される。また、backtrace_on_stack_overflowを有効化する。 Rustのdebug_assertionsフラグとは独立。

backtrace

backtrace_on_stack_overflowだけを有効化する。

japanese

システム言語を日本語にする。 Erg内部のオプション、ヘルプ(help, copyright, licenseなど)、エラー表示は日本語化が保証される。

simplified_chinese

システム言語を簡体字中国語に設定します。 Erg 内部オプション、ヘルプ (ヘルプ、著作権、ライセンスなど)、エラーは簡体字中国語で表示されます。

traditional_chinese

システム言語を繁体字中国語に設定します。 Erg 内部オプション、ヘルプ (ヘルプ、著作権、ライセンスなど)、エラーは繁体字中国語で表示されます。

unicode/pretty

コンパイラが表示をリッチにする。

pre-commit

テストを実行する為に使用される。このフラグがないと特定の環境においてテストが失敗する。バグワークアラウンドである。

large_thread

スレッドのスタックサイズを大きくする。Windowsでの実行やテスト実行のために使用される。

els

--language-serverオプションが利用可能になる。 erg --language-serverでLanguage Serverが起動する。

py_compatible

Python互換モードを有効にする。APIや文法の一部がPythonと互換になる。pylyzerのために使用される。

Ergリポジトリのディレクトリ構造

badge

 └─┬ assets: 画像など
   ├─ CODE_OF_CONDUCT: 行動規範
   ├─┬ crates
   │ ├─ els: Erg Language Server (言語サーバー)
   │ ├─ erg_common: 共通のユーティリティ
   │ ├─ erg_compiler: コンパイラ、**Ergのコア**
   │ └─ erg_parser: パーサー
   ├─┬ doc
   │ ├─┬ EN
   │ │ ├─ API: Erg標準API
   │ │ ├─ compiler: コンパイラの実装に関して
   │ │ ├─ dev_guide: 開発・貢献者向けガイド
   │ │ ├─ python: Ergの開発に必要なPythonの知識
   │ │ ├─ syntax: Ergの文法
   │ │ └─ tools: Ergのコマンドラインツールに関して
   │ └─┬ JA
   │  ...
   ├─ examples: サンプルコード
   ├─ library: Ergスクリプトによるライブラリ
   ├─ src: main.rsとドライバの置かれたディレクトリ
   └─ tests: テストコード

書式

badge

以下のルールに従っていないドキュメントはすべて修正の対象となる。

  • コードコメント、または内部用のドキュメントは、である調で書く。
  • 外部(一般ユーザー)に見せるドキュメントは、ですます調で書く。
  • ドキュメント内で初出の用語は、必ず定義や意味、またはリンクを併記する。
  • ただし書きとしての()は、補助的ではあるものの本文の理解に必要な文の場合のみ使用し、本文の理解に必須でない文は脚注を使用する1
  • ドキュメントの内容が古くなっていた場合は、この方法に従って更新する。
  • syntax以下のファイルは、ドキュメントに含めないものを除きファイル名の頭に連番を付ける。
    • 挿入や入れ替えを自動で行うスクリプトがdoc/script内に存在する。

1 脚注の書き方はこれを参照すること。

Ergコンパイラのアプリケーションへの組み込み

badge

貴方のアプリケーションへErgを組み込むことは簡単です。

[dependencies]
erg = "0.5.12" # choose latest version
use erg::DummyVM;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut vm = DummyVM::default();
    let _res: String = vm.eval("print! \"Hello, world!\"")?;
    Ok(())
}

実行にはPythonが必要です。

ランタイムと接続されないコンパイラ単体のバージョンもあります。

[dependencies]
erg_compiler = "0.5.12" # choose latest version
use erg_compiler::Compiler;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut compiler = Compiler::default();
    let code = compiler.compile("print! \"Hello, world!\"", "exec")?;
    code.dump_as_pyc("o.pyc", None)?;
    Ok(())
}

CompilerCodeObjという構造体を出力します。これは一般的にはあまり役に立たないので、Pythonのスクリプトを出力するTranspilerを使うのも良いでしょう。

use erg_compiler::Transpiler;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut transpiler = Transpiler::default();
    let script = transpiler.transpile("print! \"Hello, world!\"", "exec")?;
    println!("{}", script.code);
    Ok(())
}

その他にも、HIR(高レベル中間表現)を出力するHIRBuilderや、AST(抽象構文木)を出力するASTBuilderもあります。

use erg_compiler::HIRBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut builder = HIRBuilder::default();
    let artifact = builder.build("print! \"Hello, world!\"", "exec")?;
    println!("{}", artifact.hir);
    Ok(())
}
use erg_compiler::ASTBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut builder = ASTBuilder::default();
    let ast = builder.build("print! \"Hello, world!\"")?;
    println!("{}", ast);
    Ok(())
}

構文解析以降の意味解析を行う構造体は、ContextProviderというトレイトを実装しています。モジュール内の変数情報を得ることなどができます。

use erg_compiler::Transpiler;
use erg_compiler::context::ContextProvider;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut transpiler = Transpiler::default();
    let script = transpiler.transpile("i = 0", "exec")?;
    println!("{}", script.code);
    let typ = transpiler.get_var_info("i").0.t;
    println!("{typ}");
    Ok(())
}

開発環境

badge

インストールが必要なもの

  • Rust (installed with rustup)

    • ver >= 1.64.0
    • 2021 edition
  • pre-commit

pre-commitを使ってclippyのチェックやテストを自動で行わせています。 バグがなくても最初の実行でチェックが失敗する場合があります。その場合はもう一度コミットを試みてください。

  • Python3インタープリタ (3.7~3.11)

様々なバージョンでErgの挙動を検査したい場合は pyenv 等の導入をお勧めします。

推奨

  • エディタ: Visual Studio Code
  • VSCode拡張機能: Rust-analyzer, GitLens, Git Graph, GitHub Pull Requests and Issues, Markdown All in One, markdownlint
  • OS: Windows 10/11 | Ubuntu 20.04/22.04 | macOS Monterey/Ventura

メッセージの多言語化

badge

Ergはメッセージ(スタート、オプション、ドキュメント、ヒント、警告、エラーメッセージなど)の多言語化を進めています。 このプロジェクトは、RustやErgの詳しい知識がなくても参加することができます。ぜひ協力をお願いします。

以下に、多言語化の方法を説明します。

switch_lang!を探す

Ergのソースコードの中で、switch_lang!という項目を探します(grepやエディタの検索機能を使ってください)。 以下のようなものが見つかるはずです。


#![allow(unused)]
fn main() {
switch_lang!(
    "japanese" => format!("この機能({name})はまだ正式に提供されていません"),
    "english" => format!("this feature({name}) is not implemented yet"),
),
}

このメッセージは現在、日本語と英語のみでサポートされています。試しに簡体字のメッセージを追加してみましょう。

メッセージを追加する

他の言語の内容を見ながら、翻訳されたメッセージを追加してください。最後にカンマ(,)を忘れないでください。


#![allow(unused)]
fn main() {
switch_lang!(
    "japanese" => format!("この機能({name})はまだ正式に提供されていません"),
    "simplified_chinese" => format!("该功能({name})还没有正式提供"),
    "english" => format!("this feature({name}) is not implemented yet"),
),
}

なお、英語はデフォルトであり、必ず最後に来るようにします。 {name} の部分は Rust のフォーマット機能で、変数の内容 (name) を文字列に埋め込むことができます。

ビルド

では、--features simplified_chinese オプションを付けてビルドしてみましょう。

screenshot_i18n_messages

やりましたね!

FAQ

Q: {RED}{foo}{RESET} のような指定は何を意味するのでしょうか? A: {RED}以降が赤色で表示されます。{RESET}で色を元に戻します。

Q: 自分の言語を追加したい場合、"simplified_chinese" =>の部分はどのように置き換えればよいですか? A: 現在、以下の言語がサポートされています。

  • "english" (デフォルト)
  • "japanese" (日本語)
  • "simplified_chinese" (簡体字中国語)
  • "traditional_chinese"(繁体字中国語)

これら以外の言語を追加したい場合は、リクエストしてください。

Rustコードに関するガイドライン

badge

ローカルルール

  • デバッグ用の出力にはlog!を使用する(release時にも必要な出力処理はprintln!BufWriter等を使用する)。
  • 未使用・または内部用の(privateかつ特定の機能のみに使用する)変数・メソッドは先頭に_を1つ付ける。予約語との衝突を回避したい場合は後ろに_を1つ付ける。
  • clippyを使用する。ただしclippyのルールの中にはあまり意味のないものもあるので、レベルがdenyでない場合は#[allow(clippy::...)]を使用して無視しても良い。

奨励されるコード

  • 数値の列挙やboolの代わりにドメイン固有のEnumを定義して使う。
  • アクセス修飾子は必要最小限のものとする。公開する場合でもpub(mod)pub(crate)を優先的に使用する。
  • for式でのiterableオブジェクトは明示的にイテレータに変換する(for i in xではなくfor i in x.iter())。
  • 遅延評価。例えば、defaultがリテラル以外の場合はunwrap_orではなくunwrap_or_elseを使用する。

奨励されないコード

  • return type overloadingを多用する。具体的には自明でない.intoを多用するコード。これは型推論結果が直感に反する場合があるためである。この場合は代わりにfromを使うことを推奨する。
  • Derefを多用する。これは実質的に継承と同じ問題を引き起こす。

文脈により判断が変わるコード

  • 未使用のヘルパーメソッドを定義する。
  • unwrap, cloneを多用する。場合によってはそうするより他にないものもある。

依存関係

極力依存関係は少なくし、必要なものは自前で実装する。実装が極めて困難、またはハードウェア依存性が高いなどの場合のみ外部依存を許す(例: libc, winapi)。 また、外部依存がないcrateは使用して良い(例: unicode-xid)。そうでない場合はoptional dependencyとして認める場合がある。いずれの場合も、良くメンテナンスされており、使用例の多いものを選択する。

また、このルールが適用されるのはErgコンパイラ本体のみであり、Ergのツールやライブラリは自由に依存関係を追加して良い。

テスト

テストは、コードの品質を保証するための重要な要素です。

テストは以下のコマンドから実行してください。

cargo test --features large_thread

cargoはテスト実行用のスレッドを小さく取るため、large_threadフラグを付けてスタックオーバーフローを回避します。

テストの配置場所

実装した機能に合わせて配置してください。パーサーに対するテストはerg_parser/tests以下に、コンパイラ(型検査器等)のテストはerg_compiler/tests以下に、ユーザーが直接使える言語機能のテストはerg/tests以下に配置してください(ただし現在はテストの整備中で、必ずしもこの規則に従ってテストが配置されているわけではありません)。

テストの書き方

テストは大きく分けて2種類あります。positive(正常系)テストとnegative(異常系)テストです。 正常系テストは、コンパイラが意図したように動作するか確かめるためのテストで、異常系テストは不正な入力に対してコンパイラが適切にエラーを出力するか確かめるためのテストです。 プログラミング言語処理系はその性質上、あらゆるソフトウェアの中でも特に不正な入力を受けやすく、かつエラーを常にユーザーに提示しなくてはならないので、後者にも気を配る必要があります。

あなたが言語に新しい機能を追加するならば、少なくとも1つの正常系テストを書く必要があります。異常系テストも出来れば書いてください。

ignore属性

Erg開発チームはpre-commitの導入を推奨しています。 これにより、コミット前に毎回テストが走るためバグの混入を未然に防ぐことができますが、幾つかのテストは時間がかかるためコミットが遅くなってしまいます。

そこで、重いテストないし失敗する蓋然性が低いテストには#[ignore]属性を付けています。 #[ignore]属性を付けたテストはcargo testでは実行されませんが、cargo test -- --include-ignoredで実行することができます。 これらのテストはCIで実行されるため、ローカルPCで実行する必要はありません。

トラブルシューティング

badge

Q: ローカルでのビルドは成功したが、GitHub Actionsのビルドが失敗する

A: あなたの作業しているブランチがmainの変更に追従していない可能性があります。

Q: pre-commitのチェックが失敗する

A: もう一度コミットを試みてください。最初の1回は失敗することがあります。何度やっても失敗する場合、コードにバグが含まれている可能性があります。

Q: pre-commitのテストで「リンク失敗」となる

A: 別のプロセスでcargoが実行中でないか確認してください。

Q: build.rsの実行に失敗する

A: build.rsが動作するディレクトリ上に余計なファイル・ディレクトリ(__pychache__など)がないか確認してください。

バージョン

badge

Ergコンパイラはセマンティックバージョニングに従ってバージョン番号を付ける。 ただし、バージョン0の間は通常と異なるルールが適用される(セマンティックバージョニングよりも細かいルールに従う)。 ここで注意すべき点として、Ergには2種類の互換性がある。言語仕様の互換性を示す 仕様互換性 と、コンパイラ等の(公開)APIの互換性を示す 内部互換性 である。

  • バージョン0の間は、マイナーリリースで仕様・内部互換性が壊れる可能性がある。これは通常のセマンティックバージョニングと同じである。
  • バージョン0の間は、パッチリリースで仕様互換性が壊れることはないが、内部互換性の保証はない。
  • 新機能は主にマイナーリリースで追加されるが、些細な言語機能である場合、またはコンパイラの機能の場合はパッチリリースでも追加されうる。

リリースサイクル

  • パッチリリースは1~2週間に一度程度行われる。ただし重篤なバグが発見された場合、前倒しされる可能性もある。
  • マイナーリリースはパッチリリース10数回程度、すなわち3~6ヶ月に一度行われる。
  • メジャーリリースの間隔は不定である。バージョン1リリースの予定は現在のところ定まっていない。

nightly, betaリリース

Ergでは不定期にnightlyリリースとbetaリリースを行う。nightlyリリースは新しいパッチリリースの先行公開版であり、betaリリースは新しいマイナー/メジャーリリースの先行公開版である。 nightlyバージョン及びbetaバージョンはcrates.io上で公開される。betaバージョンの場合はGitHub releasesでも公開される。

nightlyバージョンの形式は0.x.y-nightly.zである。同一のパッチリリースに対して複数のnightlyリリースが存在することがあり、その場合は新しくなるにつれてzが増加する。betaバージョンも同様である。

nightlyリリースはほぼ隔日で行われる(コンパイラ本体に対する変更のない日は行われない)が、betaリリースは不定期である。ただし、betaリリースは一度公開されると、ほぼ隔日で新しいbetaリリースが公開される。