# Culebra ツール `culebra`バイナリはツールチェーンそのものです。プログラムを実行する同じ 実行ファイルが、テストランナー・リンタ・フォーマッタ・デバッグアダプタ・言語サーバ、 そしてリファレンス文書そのものも兼ねています。この文書はそれらの 開発用サブコマンドのリファレンスです。 | サブコマンド | 役割 | 参照 | |---|---|---| | `culebra init` | このディレクトリとエディタをセットアップする | [§4](#手早いセットアップ-culebra-init) | | `culebra test [paths...]` | テストとdoctestを実行する | [§1](#1-テスト-culebra-test) | | `culebra lint [paths...]` | 実行せずに静的な問題を報告する | [§2](#2-lint-culebra-lint) | | `culebra fmt [paths...]` | ソースを正準スタイルに整形する | [§3](#3-フォーマット-culebra-fmt) | | `culebra dap` | Debug Adapter Protocolをstdioで話す | [§4](#4-デバッグ-culebra-dap) | | `culebra lsp` | エディタに診断・ホバー・整形をstdioで提供する | [§8](#8-エディタ支援-culebra-lsp) | | `culebra docs [topic]` | 埋め込まれたリファレンスを読む・検索する | [§5](#5-ドキュメントを読む-culebra-docs) | | `culebra serve [-p PORT] [-d DIR]` | ディレクトリを静的ファイルとして配信する | [§6](#6-静的ファイルの配信-culebra-serve) | | `culebra build -o ` | AOTコンパイルして単体実行ファイルを作る | [`deployment.ja.md` §1](deployment.ja.md#1-standalone-バイナリビルドculebra-build) | | `culebra wrap` | 自前のC++ クラスを組み込んだ拡張バイナリを作る | [`deployment.ja.md` §3](deployment.ja.md#3-c-ライブラリのラッピングculebra-wrap) | 素の`culebra [flags] script.cul`形式 — `--jit`・`-O0`..`-O3`・`--ast`・ `--shell`など — は [言語仕様 §22](language.ja.md#22-コマンドラインインタフェース) が規定します。 同じツール群の読み物としての導入は [`handbook.ja.md` §15](handbook.ja.md#15-ツール-test-lint-fmt-デバッグ) にあります。 ブラウザのPlaygroundだけはバイナリではありません。特定のプログラムを開くURL、 つまりShareボタンが書き出す形式そのものは [§7](#7-playgroundへのリンク) が規定します。 目次 ---- 1. [テスト (`culebra test`)](#1-テスト-culebra-test) * [テストの書き方](#テストの書き方) * [実行](#実行) * [doctest](#doctest) 2. [Lint (`culebra lint`)](#2-lint-culebra-lint) 3. [フォーマット (`culebra fmt`)](#3-フォーマット-culebra-fmt) 4. [デバッグ (`culebra dap`)](#4-デバッグ-culebra-dap) * [手早いセットアップ: `culebra init`](#手早いセットアップ-culebra-init) * [VSCode](#vscode) * [Vim (vimspector)](#vim-vimspector) * [Zed](#zed) 5. [ドキュメントを読む (`culebra docs`)](#5-ドキュメントを読む-culebra-docs) 6. [静的ファイルの配信 (`culebra serve`)](#6-静的ファイルの配信-culebra-serve) 7. [Playgroundへのリンク](#7-playgroundへのリンク) * [プロジェクト](#プロジェクト) * [テキスト](#テキスト) * [ページ](#ページ) * [Shareボタン](#shareボタン) 8. [エディタ支援 (`culebra lsp`)](#8-エディタ支援-culebra-lsp) * [設定](#設定) --- ## 1. テスト (`culebra test`) `culebra test [paths...]`はテストファイルを探索して実行します。`test()`・ `@test`・`@parametrize`・マッチャ群・依存注入によるフィクスチャがすべて 使えます。 ### テストの書き方 書き方は3通りあります。呼び出し形式と`@test`デコレータは等価なので、 呼び出し側が読みやすい方を選んでください。`@parametrize`はケースごとに 1つのテストを登録します。 ```culebra # doctest: skip # tests/test_string.cul # 呼び出し形式 test("interpolation embeds Long", fn () { let x = 42 assert_eq("hi {x}", "hi 42") }) # デコレータ形式 — 関数名がテスト名になる @test fn interpolation_embeds_float() { let pi = 3.14 assert_eq("π = {pi}", "π = 3.14") } # パラメタライズ — ケースごとに 1 テスト、名前は `[i]` @parametrize([(1, 2, 3), (2, 3, 5), (10, 20, 30)]) fn adds_correctly(a, b, want) { assert_eq(a + b, want) } ``` **`describe`のネストはありません。** ファイルパス(`tests/strings/`)と テスト名中の`/`(`"Array/push: appends element"`)でグループ化します。 **フィクスチャはDIで。** テストの位置引数は周囲の環境から名前で解決され ます。envにある任意の関数がフィクスチャになれ、デコレータは不要です。 フィクスチャ自身がフィクスチャを取ることもできます。 ```culebra # doctest: skip fn db() { {users: [], next_id: 1} } fn user(db) { db.users.push({id: 1, name: "alice"}) db.users[0] } @test fn user_has_name(user) { assert_eq(user.name, "alice") } ``` 1つのテストの中では各フィクスチャは**一度だけ**評価され、直接・推移的な 複数の言及が同じインスタンスを共有します。テストをまたぐと新しく作られます。 **後始末はクラスの`drop`で。** 後始末が要るリソースは`drop`メソッドを 持つクラスに包みます([`language.ja.md` §17](language.ja.md#17-メモリモデル))。 テスト終了時にフィクスチャキャッシュが解放されると、ランタイムの参照カウント による確定処理が走ります。 ```culebra # doctest: skip class TestDB { new() { self.conn = Database.connect("memory") } drop() { self.conn.close() } users() { self.conn.users } } fn db() { TestDB.new() } @test fn user_count(db) { db.users().create("alice") assert_eq(db.users().count(), 1) # テスト終了時に db が drop -> conn.close() } ``` フィクスチャ本体に書いた`defer`はフィクスチャ関数がreturnした時点 (テスト実行より前)で発火してしまうので、後始末にはクラスの`drop`が 適切です。ファイルをまたいで共有する長命な状態(一度だけ読み込むモデル等)は モジュールのトップレベルに置きます。モジュールシステムが束縛をキャッシュする ためです。 **マッチャ。** 表明にはマッチャ群を使います。`assert`キーワードや組み込みは ありません。マッチャは **3バックエンド共通のグローバル**(`inspect`や `Math`と同じく全環境に束縛される)なので、`culebra script.cul`・ `culebra --jit script.cul`・`culebra build`・`culebra test`のいずれでも 同じように動きます: ```culebra # doctest: skip assert_eq(arr.len(), 3) # == ; 失敗時に両辺を表示 assert_throws("TypeError", fn () { let _ = 1 + 'b' }) assert_close(0.1 + 0.2, 0.3, 1e-9) # |a - b| <= tol ``` マッチャの全一覧(`assert_true`/`false`/`ne`/`lt`/`le`/`gt`/`ge`と `__eq__`/`__lt__`のディスパッチ規則)は [`stdlib.ja.md` §13](stdlib.ja.md#13-matchers)。 **本番の不変条件。** テストスイート外の`if (!cond) throw {...}`検査は `if`/`throw`を直接書きます(Go流。 [`language.ja.md` §15](language.ja.md#15-エラー処理) 参照)。本番ビルドで 無効化する`assert`キーワードのようなものはありません。 ### 実行 このサブコマンド経由で起動すると、`test` / `@test` / `@parametrize`が常時 使えるマッチャ群と並んで**環境に備わったグローバル**になります(import不要)。 これらは1回の実行のあいだだけ、どのレーンにもある標準ライブラリの名前に 上乗せする形で束縛されます。 ```sh culebra test # カレントディレクトリから探索して実行 culebra test tests/strings/ # サブツリーだけ実行 culebra test --filter "Array/push" # 名前の部分一致で絞る culebra test --reporter json # NDJSON 出力(1 行 1 JSON) culebra test --bail # 最初の失敗で停止 culebra test --bail 3 # 3 件失敗したら停止 culebra test --list # 探索のみ。テスト名を表示 culebra test --doc docs # markdown 中の doctest を実行 culebra test --doc --vm docs # ... をバイトコード VM で(--jit も可) culebra test --doc --jit --jobs 8 docs # ... を 8 プロセスで ``` 探索規則: ファイルを指すパスはそのまま対象になり、ディレクトリを指すパスは `test_*.cul`にマッチするファイルを再帰的に探します。終了コードは全て通れば `0`、1つでも失敗すれば`1`です。 **ファイルは1つで1つのプログラムです。** ファイルごとに独立したスコープが 与えられ、ランナーは次のファイルを読み込む前にそのファイルのテストを走らせ ます。トップレベルに書いたものはそのファイルのもので(`mut range = 5`と 書いても他のファイルでは`range`は関数のまま)、クラス・オーバーロード・ `Sys.script`・`Dir.embedded(...)`が基準にするディレクトリも同様です。ある ファイルで宣言したフィクスチャは別のファイルからは見えません。パラメータ 経由で渡すか、必要なファイルごとに宣言してください。`import`も使えて、 スクリプトのときと同じくモジュール一式が1つの単位として走ります。 ファイルのトップレベルはスクリプトではなく**セッション**のスコープで走り ます。スコープの読み方はどちらでも同じで、関数の中の裸の代入は、既に何かが その名前を宣言していない限りその関数のローカル宣言です。ただし帰結が1つ あります。ランナーは`test` / `parametrize`とマッチャ群をファイルより先に 宣言するので、それらの名前は宣言済みとして数えられます。関数の中からその 1つに裸で代入すると再代入になって拒否されます。スクリプトなら同じ行が ローカルを宣言するところです。 **レポータ。** 既定は人間向けです。`--reporter json`は1行1 JSON オブジェクト(NDJSON)を出力します。エージェントループやCIに向きます: ``` {"event":"test_pass","name":"adds_correctly","source":"tests/test_math.cul","stdout":""} {"event":"test_fail","name":"divides_correctly","kind":"AssertionError", "message":"assert_eq failed:\n left: 3\n right: 4","line":12,"col":3,"stdout":""} {"event":"run_end","passed":42,"failed":1,"files":7,"errored_files":0,"bailed":false} ``` `files`は走ったテストファイルの本数です(`--list`がパスを並べる本数と同じ)。 `--doc`実行が数えるのはファイルではなくブロックなので、その`run_end`に `files`はありません。 テスト内のユーザ`inspect(...)`はNDJSONストリームに混ざらず、イベントの `stdout`フィールドに取り込まれます。失敗イベントには失敗行を`>`で示した `snippet`が付きます。 ### doctest `culebra test --doc `は``配下のmarkdownからすべての ` ```culebra `ブロックを取り出し、新しいVMランタイムで実行して、下記の マーカーと出力を突き合わせます。`handbook.ja.md`・`language.ja.md`・ `stdlib.ja.md`の全ブロックがこの規約に従っています: - `# => ` — 標準出力の期待値(1行) - `# => |`に続く`# `行 — 複数行の期待値 - `# !! ` — `throw`の期待値(部分一致) - `# doctest: `(ブロック先頭行)— モード: - `skip` — 説明用。実行しない(*Planned* 機能など) - `compile-only` — 構文検査のみ - `jit-only` / `aot-only` — バックエンド指定 ブロック間は独立で`setup` / `teardown`はありません。複数ステップが必要な 例は1ブロックに収めてください。ランナーが現在解釈するのは`skip`のみで、 `compile-only`とバックエンド指定は予約済み(該当ブロックは通常どおり実行 されます)。 `--jit`は同じブロックをVMではなくLLVM JITで走らせます。ドキュメントの例が、 それを実行しうるすべてのエンジンで同じ出力・同じthrowになることを確認 できます。`--doc`を付けない`culebra test`(ユニットテストのランナー)は VMで走ります。 `--jobs `はブロックを`n`個のプロセスに分けて実行します。各プロセスは ブロック一覧の連続した区間を担当し、親がその出力をソース順に並べ直すので、 レポートの見え方は逐次実行と同じです。JITレーンは1ブロックにつき1つの LLVMモジュールをコンパイルするため、このフラグはそこを狙ったものです。 `--bail`と`--list`は逐次のままです。互いに競走するプロセス間では 「最初の失敗で止める」が意味を持たず、列挙は何も実行しないためです。 例を書くときに知っておく価値のある帰結が2つあります。式を単に置いても 何も出力されないので、検証される例は`inspect(...)`か`println(...)`を 通す必要があります。そしてCulebraでは`#`と同様に`//`も行コメントを 開始するため、`// => 値`というマーカーは構文としては通っても**期待値として 認識されず**、そのブロックは無検証で実行されます。 **予定**: `culebra test`経由で走らないコード向けの明示的な `import { test } from "std/test"`、`--jit` / `--vm`に並ぶAOTのdoctest レーン。 --- ## 2. Lint (`culebra lint`) `culebra lint [paths...]`はプログラムを**実行せずに**静的な問題を報告し、 CIがゲートにできるよう非ゼロで終了します(0 = 問題なし、1 = 警告のみ、 2 = エラー)。各バックエンドが既に走らせているロード段階の静的解析を再利用 するので、報告されるエラーは実行時に中断する原因とちょうど同じものです。 その上に助言的な警告が乗ります。 ```bash culebra lint app.cul # app.cul:12:7: warning: unused variable 'tmp' # app.cul:20:3: error: undefined variable 'reuslt' culebra lint . # カレントディレクトリ配下の .culを再帰的に # (`culebra fmt -i .`と同じ) ``` `.cul`を1つも含まないパスはcleanではなくexit 2になります。ディレクトリ の打ち間違いがゲートを素通りしないためです。 `test_*.cul`(`culebra test`が探索するのと同じ規約)という名前のファイルは、 そのランナーが走らせるのと同じ扱いでlintされるので、`test`と`parametrize`は 束縛済みとして数えられます。それ以外のファイルでは従来どおり未定義の名前で あり、`culebra lint .`は1回のパスで両方を正しく判定します。 現在報告する内容: - **エラー** — 確実に失敗する健全な集合: 場所が不正な`break` / `continue` / `return`、不正なパラメータ・代入形式、パラメータやクラスメンバの重複、 シャドーイング、どこにも束縛されていない名前の読み取り(未定義変数の 部分集合)。これらは元々どの実行も中断させるもので、`lint`は最初の1件で 止まる代わりに全件を一度に見せます。 - **警告** — 実行を止めない助言: - **未使用のローカル変数** — 関数内の`let` / `mut`で、一度も読まれない もの。 - **未使用のトップレベル束縛** — モジュールが読みも再exportもしない トップレベルの`let` / `mut`。関数・クラス・enum・trait宣言はモジュールの export面なので対象外です。 - **未使用のimport** — `import`した名前をモジュールが使っていない。 - **到達不能コード** — 同じブロック内で先に`return` / `throw` / `break` / `continue`があるため決して実行されない文。および、ガードの無い手前の アームや節(`_`、または裸の束縛)がすべての値を受け取るため決して実行 されない`match`のアームと`catch`節。 - **enumのmatchの網羅漏れ** — `match`のアームがenumの一部のvariantしか 名指ししておらず、catch-all(`_`、裸の束縛、enum自身を指す型パターン) も無いケース。`match`は非マッチ時に例外を投げず`nil`を返すため、後から 追加したvariantや単純な書き忘れが黙って通り抜けます。全アームの パターンが同一ファイル内で宣言された1つのenumを曖昧さ無く指している ときのみ検査します(同名variantを複数のenumが持つ場合はskip)。 ガード付きアーム(`Circle(r) if r > 0 => …`)は、ガードが実行時に rejectしうるため、名指ししたvariantを網羅したとは数えません。 先頭のアンダースコア(`_x`、または裸のシンク`_`)は「意図的に未使用」の 印で、決してフラグされません。**パラメータは対象外です**: Culebraで 未使用のパラメータは圧倒的に意図的だからです — 多重ディスパッチの節や メソッドのシグネチャがアリティを固定しますし、高階のコールバック (ルートハンドラの`fn(req)`、`|i| 4.0`)は宣言だけして使わない引数を 持ちます。検査してもノイズにしかなりません。 - **イディオム警告** — 例外がなく誤検出も起こらない書き換え: - **冗長な自己代入** — `x = x + 1`は遠回りに書いた`x += 1`です (`-=`・`*=`・`/=`も同様)。 - **`.size()`とゼロの比較** — `xs.size() == 0` / `> 0` / `!= 0`が 問うているのは`.empty()` / `!xs.empty()`の方が直接答えます。 - **`range(0, n)`** — 明示的なゼロ始点は冗長で、`range(n)`が既に 同じ意味です。 同じ精神の書き換えがもう2つあります — 三項演算子で書けるはずの `if`/`else`、`enumerate()`で置き換えられるはずの手動インデックス ループ — が、どちらも例外が実在するため、このチェックが要求する 誤検出ゼロの基準を満たせず、 [`quick-guide.ja.md` §3](quick-guide.ja.md#3-持ち込むと外れる習慣) の地の文にとどめています。 `culebra lint --fix `は未使用import行を機械的に削除します。 無人で自動修正して安全なのはこの警告だけです。死んだ`import`を消しても 振る舞いは変わり得ない一方、未使用の`let`/`mut`は初期化式が副作用を持ち うるからです。他の警告はすべて報告のみです。編集後は修正済みソースを 再パース・再lintし、importが消えて新しいエラーも出ていないと確認できた 場合にだけ書き出します — `culebra fmt`と同じ再パースの安全網です。 削除は行単位なので、対象になるのはimport 1つだけがある行に限られます。 `;`で他の文とつながったimportは報告のみで手つかずのまま残します: その行を消すと隣の文まで巻き添えになり、しかも文が1つ消えてもファイルは 同じようにパースもlintも通るため、再チェックでは気づけないからです。 ```bash culebra lint --fix app.cul # app.cul: fixed 1 unused import culebra lint --fix joined.cul # joined.cul: --fix skipped 1 unused import sharing a line with other code # joined.cul:1:8: warning: unused import 'Math' ``` 未知のフラグはパスではなくエラー(exit 2)です: `culebra lint --fixx app.cul` は、修正が無効なままlintを続けるのではなく打ち間違いを報告します。 これらの診断は、`culebra lsp`を通して入力中のエディタにも表示されます ([§8](#8-エディタ支援-culebra-lsp))。予定: インラインの`# lint: ignore`による抑制。 --- ## 3. フォーマット (`culebra fmt`) `culebra fmt [paths...]`はソースを1つの正準スタイルに整形します: 演算子まわりの空白の正規化、2スペースインデント、ブレースブロックの複数行 配置、行幅を超えた引数リスト/コレクションリテラルの折り返し。方針は固定で 設定は不要です(スタイルフラグはありません)。 ```bash culebra fmt app.cul # 整形結果を標準出力へ culebra fmt -i app.cul # ファイルをその場で書き換え culebra fmt -i . # カレントディレクトリ配下の .culを全て整形 culebra fmt --check app.cul # 未整形ならexit 1(CIゲート) culebra fmt -l src/*.cul # 変更されるファイルを列挙 cat app.cul | culebra fmt - # stdin -> stdout(エディタの保存時整形) ``` ディレクトリ引数は`.cul`を再帰的に走査するので、`culebra fmt -i .`で プロジェクト全体を整形し、`culebra fmt --check .`でCIをゲートできます。 `.cul`を1つも含まないパスはエラー(exit 2)なので、パスの打ち間違いは 黙って何も整形せずに成功するのではなく、その場で失敗します。 出力モードは組み合わせられます: `culebra fmt -i -l .`は書き換えが必要な ファイルを書き換え**かつ**その名前を列挙し(`gofmt -l -w`と同じ)、 `-i --check`は同じことを出力なしで行います。`-l` / `--check`を付けた場合、整形が異なるファイルが 1つでもあれば終了コードは1なので、書き換えた後でもCIのゲートになります。 整形結果が標準出力に出るのは、他の出力モードを何も指定しなかったときだけです。 `-`はstdinを読み、単独でのみ使えます: ファイルパスとの併用(入力2つに stdout 1つ)や`-i`との併用(書き換える対象が無い)は、どちらかを黙って 優先するのではなくexit 2になります。パスを指定しない`culebra fmt -i`も 同じ扱いです。未知のフラグもファイル名と解釈せずエラーにします — `culebra fmt --wirte app.cul`はexit 2で何も整形しないので、打ち間違いが 成功した実行に見えることはありません。 コメントは保存されます: 前置コメントは導入する文の上に、行末コメントは同じ 行に残り、文の間の空行1行も保たれます(連続する空行は1行に畳まれます)。 match / condのアーム、クラス・trait・enumのメンバ、分配パターン、 パラメータリストはすべて正規化され、長い二項式やメソッドチェーンは行幅で 折り返されます。`if` / `while` / `match` のヘッドは括弧を持たないため、 折り返しは自身のチェーンの `||` / `&&` / `??` の継ぎ目だけで起き(比較の 左右の間では折りません)、継続行は本体より1段深くインデントされます。 コメントを持つオブジェクトリテラルは1要素ずつ行を 分けて出力され、コメントは書いた位置に残ります。コメントの無いリテラルは コンパクトなままです。 数値だけを並べた配列リテラルは表として扱われます。1行に収まらないときは、 数値ごとに行を分けるのではなく行を埋めていきます。端数になる最後の行を 除いてどの行も同じ個数を持ち、数値は右寄せされるので列を縦に読めます。 個数は、1行目に書いた個数です(その個数が行幅に収まる場合)。行を 終わらせたい位置で改行すれば、残りは`fmt`が並べます: ```culebra let colors = [ 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 0, 255, 128, 128, 128, 64, 64, 64, 32, 32, 32, ] ``` 数値を1行に1個ずつ書いた表や、1行目が長すぎて保てない表には、従うべき 行がありません。全体が1行に収まるなら1行に、収まらなければ行幅に収まる 最大の2の冪になり、行の先頭が切りのよいインデックスになります: ```culebra let lengths = [ 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, ] ``` 1行に収まる表は、どう改行して書かれていても1行で出力されます。それ以外の 要素(名前・文字列・入れ子の配列)を含む配列は1要素ずつ行を分けて 出力され、コメントを含む配列は書かれた行の並びのまま残ります。 仕組み: ソースをパースし、構文木から再出力し、それを**再パースして元と 比較**します。整形がプログラムの意味を変える場合や、コメントを落とす・ 複製する場合、`fmt`は破壊を避けてファイルに触れず拒否します。整形は冪等で、 2回走らせた結果は1回の結果と同じです。 ### エディタ統合 エディタは、言語サーバ([§8](#8-エディタ支援-culebra-lsp))かstdin形式 (`culebra fmt -`) のどちらかで整形します。どちらもバッファ全体を整形し、 パース/安全性エラーの時はバッファに触れません。 - **VSCode** — 同梱拡張が言語サーバを通して整形するので、 **Format Document** と`editor.formatOnSave`が`.cul`でそのまま動きます (導入は`culebra init`、またはソースチェックアウトから `misc/vscode/install.sh`)。 - **Zed** — 拡張が登録する言語サーバが`.cul`を整形します。設定は要りません。 - **Vim/Neovim** — 同梱の`ftplugin`が`:CulebraFmt`コマンドを提供します (カーソル位置を保持、エラー時は無変更)。保存時整形は `let g:culebra_fmt_autosave = 1`。`gq` / `'formatprg'`は意図的に使いません — パースできない範囲を空出力で置き換えてしまうためです。Neovim 0.10以降は `:lua vim.lsp.buf.format()`で言語サーバを通しても整形できます。 - 保存時フックを持つ他のエディタも、同じようにバッファを`culebra fmt -`に 通せます。 --- ## 4. デバッグ (`culebra dap`) Culebraは [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) (DAP) サーバを同梱しています。これにより、DAP対応エディタ — VSCode・Vim (vimspector)・Zed・Emacs (dap-mode)・Helixなど — でブレークポイント・ ステップ実行・変数の確認をビジュアルに行えます。1つのアダプタで全エディタに対応します。 ``` culebra dap # stdin/stdoutでDAPを話す ``` 手で実行することは稀で、通常はエディタが起動します(下記のエディタ別セットアップ参照)。 **前提** - **`culebra`を実行できること** — `PATH`を通すか、下記の各設定で絶対パスを指定。 (動作確認: `echo | culebra dap`がDAP入力待ちでハングすればOK、`Ctrl+C`で抜ける。 エラーになる場合はまずパスを直す。) - **デバッグはVMで動く** — `--jit`を付けない。アダプタはプログラムを バイトコードVMで実行する。JIT/AOTは機械語にコンパイルされソースレベルデバッグ できない。 **仕組み。** アダプタはVMのバイトコードデバッグテーブルを読んでブレークポイント・ `debugger`文・ステップ時に一時停止します。その間DAPループがエディタの要求に 応答し、再開します。プログラムの`stdout`/`stderr`はエディタのデバッグコンソール に`output`イベントとして転送されるので、プロトコルと混ざりません。 対応している機能: - **行ブレークポイント**・**条件付きブレークポイント**(式が真のときだけ停止、 例`i == 3`)・`debugger`文 - **ステップ**: continue・step over (`next`)・step in・step out・エントリ停止 - **コールスタック**: 名前付きの多フレームスタック(`inner` ← `outer` ← `main`)+ 各フレームのソース位置 - **変数**: 選択したフレームで参照中のローカル — コールスタックでフレームを 選ぶとそのフレームのローカルを参照できる。値は`inspect`と同じ形で表示する ので、classの`__str__`が走る。それが失敗した値は失敗の内容で表示され (``)、プログラムは止まっている 位置のまま変わらない - **watch / evaluate**: 選択フレームで式を評価(watchパネル・hover・デバッグ コンソール)— 例`x + y`・`arr[0]`・関数呼び出し - **変数の編集 (set variable)**: 選択フレームの`mut`変数の値を変更(`let` (不変)は拒否)。変更は実行中のプログラムに反映される - **プログラム出力** をデバッグコンソールに 全エディタ共通の補足: - デバッグ対象ファイルとその引数は`culebra dap`のコマンドラインではなく`launch` 要求の`program`フィールドで渡します。 - ブレークポイントは正準(シンボリックリンク解決済み)パスで照合するので、シンボリック リンク下のファイルに置いたブレークポイントも有効です。 - ソース中の`debugger`文はブレークポイントに関係なく停止します(設定不要の一時停止に 便利)。 ### 手早いセットアップ: `culebra init` プロジェクトディレクトリで`culebra init`を実行すると、このマシンにある VSCode・Vim・Neovim・Zedのうち見つかったものに対して、エディタ統合(シンタックス ハイライト、`culebra lsp`言語サーバ、`culebra dap`デバッグアダプタ)とAIコーディングエージェント向け 指示を導入・更新します。ペイロードはバイナリ内に同梱されているのでソース チェックアウトは不要です。Zedだけは構文文法について「ソースチェックアウト 不要」の例外です — Zedはtree-sitter文法をgitリポジトリからしか取得できない ため、`init`はローカルチェックアウトの代わりにこのバイナリのリリースタグを 指します(それが何を意味するか、Zed側UIで必要になる1手順は下のZedの節を 参照)。 `init`は何を変更するか事前に表示し、対話端末では実行前に確認を求めます。 非対話実行(パイプ・CI)や`--yes`/`-y`指定時は確認をスキップしてそのまま 適用します。何度でも再実行して構いません — 実行のたびにこのバイナリが持つ 内容で上書きするので、アップグレード後の再実行がそのまま更新手順になります。 どの統合も`.cul`を同一の規約でハイライトします(生成された1つの語彙リスト に基づく)。言語が最初から提供する大文字始まりの名前、つまり`Canvas`のような 標準ライブラリのnamespaceや`Long`のような型注釈名は組み込み型として着色し、 それ以外の大文字始まりの名前はプログラム自身が宣言したものとして着色します。 自分で書いたclass・trait・enumは、言語が知っている名前ではなく自分のものとして 読めます。Playgroundのエディタも同じ区別をします。 以下のエディタ別手順は、同じ統合をソースチェックアウトから作る手順です (拡張自体へのcontribute、または`init`が届かない環境向け)。 ### VSCode VSCodeは`.cul`のハイライトと`culebra`デバッグタイプ登録のための小さな拡張が必要です (デバッグは登録のみで、ロジックは全て`culebra dap`側にある)。 公開は不要。リポジトリに雛形とインストーラを`misc/vscode/`に同梱しています。 1. 拡張をインストール — `culebra init`、またはソースチェックアウトから: ```sh misc/vscode/install.sh ``` どちらも拡張を`.vsix`にパッケージし、`code --install-extension` でインストールします=VS Codeが公式にサポートする方法。(`~/.vscode/extensions`に フォルダを直接コピーする方法は**非サポート**で、認識されないことが多い。)`culebra`が `PATH`上にあればデバッグアダプタ設定に絶対パスを埋め込みます。`code-insiders`/`cursor`/ `codium`でも動作し、いずれのCLIも無ければExtensionsビューから`.vsix`を入れる手順を 案内します。シンタックスハイライトは`.cul`を開くだけで有効(以降の手順はデバッグ用のみ)。 文法のキーワード一覧は`just sync-grammar`がパーサから生成(Vim構文ファイルと同一ソース) するため言語からドリフトしません。 2. VSCodeを**完全終了**(Cmd+Q)して再起動 — 入れたての拡張は ウィンドウ再読み込みだけでは拾われないことがあります。 3. プロジェクトに`.vscode/launch.json`: ```jsonc { "version": "0.2.0", "configurations": [{ "type": "culebra", "request": "launch", "name": "Debug current file", "program": "${file}", "cwd": "${workspaceFolder}", "stopOnEntry": false }] } ``` 4. `.cul`を開き、ガター(行番号の左)をクリックでブレークポイント → F5。 > **拡張自体を作り込む場合**は、変更のたびに`.vsix`を入れ直す代わりに、`misc/vscode/`を > VSCodeで開き`extensionHost`のlaunch構成で F5 を押すと、拡張がライブロード > された別ウィンドウ(*Extension Development Host*)が開き、そこで`.cul`をデバッグできます。 > 単に*使いたいだけ*なら上の`install.sh`の方が簡単です。 ### Vim (vimspector) 拡張は不要 — [vimspector](https://github.com/puremourning/vimspector) を導入し、 プロジェクトルートに`.vimspector.json`を置きます(vimspectorはプロジェクト 単位の設定なので、culebra側からは配布できません)。Vimのシンタックスファイルを インストール済み(`misc/vim/install.sh`)なら、プロジェクトルートで`.cul`ファイルを 開いて`:CulebraVimspectorInit`を実行すれば生成されます — 既存の `.vimspector.json`は上書きしません。手動で作る場合は次の内容を書きます: ```json { "configurations": { "Debug file": { "adapter": { "command": ["culebra", "dap"] }, "configuration": { "request": "launch", "program": "${file}", "stopOnEntry": false } } } } ``` vimspectorは**デフォルトでキーマッピングを一切設定しない**ため、`vimrc`に 次の1行を追加してください。これが無いと``/``を押しても何も起きず、 設定に失敗したように見えます: ```vim let g:vimspector_enable_mappings = 'HUMAN' ``` これでブレークポイントは``、開始は``(`HUMAN`マッピング。 ``/``/``でstep over/in/out、``または`:VimspectorReset`で終了)。 gadgetのインストール(`:VimspectorInstall`)は不要 — 上記`command`をstdioで 直接起動します。Vimは`+python3`ビルドが必要です。 シンタックスハイライトは`culebra init`、またはソースチェックアウトから `misc/vim/install.sh`を実行してください。 ### Zed Zedはシンタックスハイライト(tree-sitter文法)にもデバッグ(デバッグアダプタは **拡張による登録が必須**=`debug.json`から任意のDAPコマンドを直接指せない)にも 拡張が必要です。両方を1つのdev extensionで提供します。 `culebra init`はVSCode/Vimと同じ方式でこの拡張を書き出します — アダプタの シムや言語設定はバイナリ内に同梱されているのでソースチェックアウトは 不要です。唯一避けられないのがZedのtree-sitter文法の取得方法です: dev extensionはgitの`repository`+`rev`+`path`しか指定できず、パーサーの ソース自体を同梱することはできません。そのため`culebra init`が書き出す 拡張は、ローカルパスの代わりにこのバイナリの`vX.Y.Z`リリースタグを公開 リポジトリ(`github.com/yhirose/culebra`)上で指します — バイナリとタグは 常に一致するので、リリース版のダウンロードに対しては正確です。文法自体を 編集する場合はソースチェックアウトが必要です — 下の「ソースチェックアウト からのビルド」を参照してください。 `init`は拡張を`~/.local/share/culebra-zed-extension`に、このプロジェクトの `.zed/debug.json`を書き出します。Zedへの導入(初回のみ): 1. コマンドパレット → **`zed: install dev extension`** → `init`が表示した ディレクトリを選択。ZedはRustシムを`wasm32-wasip2`にビルドするので、 新しめのZedに加えRustとそのターゲットが必要です: `rustup target add wasm32-wasip2`(無いと"can't find crate for core"で ビルドが落ち、アダプタが登録されません。ハイライトは無くても動きます)。 2. アップグレード後は`culebra init`を再実行して同ディレクトリを選び直して ください — リリースタグが再ピン留めされます。 その後`.cul`を開く(ハイライトされる)→ ブレークポイントを置き、デバッグ パネルから**"Debug current Culebra file"**を実行。生成される`.zed/debug.json`: ```jsonc [ { "label": "Debug current Culebra file", "adapter": "culebra", "request": "launch", "program": "$ZED_FILE", "cwd": "$ZED_WORKTREE_ROOT", "stopOnEntry": false } ] ``` > Zedのデバッガ/拡張APIはVSCodeより新しく流動的なので、キーやビルド手順が > バージョンで異なることがあります。dev extensionがビルドできない/アダプタが > 起動しない場合は、Zedのバージョンと`misc/zed/Cargo.toml`の > `zed_extension_api`バージョンの対応を確認してください。 #### ソースチェックアウトからのビルド 文法やアダプタ自体を編集するときは、`culebra init`ではなく`misc/zed/install.sh` が必要です — こちらはローカルチェックアウトの`HEAD`を`file://`経由で指すので、 未コミット・未pushの変更も即座に反映されます。上のリリースタグ方式では できないことです: ```sh misc/zed/install.sh ``` これは同じ形の拡張(文法+アダプタ+`.zed/debug.json`)を`misc/zed/`から 直接組み立てます。文法/アダプタの変更を取り込んだら再実行し — Zed側でも ディレクトリを選び直してください。実行のたびにコミットが再ピン留めされます。 --- ## 5. ドキュメントを読む (`culebra docs`) このリファレンス一式は全てバイナリにコンパイルされて入っています。 `culebra docs`はコードを実行しているのと同じビルドから答えるので、 遅れたチェックアウトも、届かないネットワークもありません。リリース 書庫に`docs/`ディレクトリが無いのは、必要ないからです。 ``` culebra docs # トピック一覧(サイズの目安つき) culebra docs stdlib # 1トピックを表示 culebra docs stdlib Scene # その中の1章をまるごと表示 culebra docs -g 'Math.wrap' # 一致したセクションを表示 culebra docs stdlib -g 'wrap' # 1トピックだけ検索 culebra docs --ja ... # 日本語版 ``` 終了ステータスはgrepの慣習に従います。`0` = 何か表示した、`1` = 一致 なし、`2` = 使い方の誤り。おかげで実在確認が、出力を読まずに1行で 書けます: ``` culebra docs -g '<名前>' >/dev/null || echo "そんなAPIは無い" ``` (ここに実際の名前を書くと、それ自身を検索することになります。この ページもcorpusの一部です。) ### 検索 `-g`は正規表現を取り、パターンに大文字が含まれない限り大小を区別 しません。コンパイルできないパターン — たいていは`get_or_put(`の ような署名の断片 — は拒否せずリテラルとして検索し、その旨をstderrに 出します。 出力の単位はセクション、つまり見出しとその下の本文です。ヒットは 一致した場所の順に並びます。`stdlib.md`の見出し一致はほぼ必ず署名で あり、それが読み手の求めていたものだからです: 1. 見出し、2. コードブロック、3. 本文。 一致セクションが8個を超えると出力は見出しの索引に降格するので、広い パターンのコストが1画面で収まり、リファレンス全体にはなりません。 見出し一致の上位数件はそれでも全文で表示されます。`--full`はこれを 上書きし、`--at <行>`は1セクションを打ち切りなしで表示します(行 番号は各ヒットの上に出るロケータのものです)。 セクションは構造上小さく保たれている(中央値21行)ので、ヒットは 通常まるごと表示されます。60行を超える分は`--at`への案内をつけて 打ち切ります。 ### 章を名指しする 2つめの引数はトピックの中の章を名指しし、その章をまるごと — 前書きも 小節も、打ち切りなしで — 表示します。これは`-g`にはできないことです。 検索は見出しごとに切るので、その仕組みの動かし方を書いた本文と、実際に 動かす署名とが、別々のヒットとして返ってくるか、両方が索引になります。 ``` culebra docs stdlib Scene # `Scene`の章ぜんぶ culebra docs stdlib Webview # `Desktop` / `Webview`はどちらの名前でも ``` 一致は大小を区別せず、章番号も無視するので`scene`でも`Scene`でも 引けます。どの章も名乗っていない名前はexit 1になりますが、その際に 実在する章の名前を並べるので、うろ覚えの名前はそこで直せます。 `-g`を添えると、章を表示する代わりに検索の範囲がその章に絞られます。 パターンが広すぎるとき「トピックを1つに絞る」の次に取る手です: ``` culebra docs stdlib Scene -g 'mesh' # Sceneの節のうち、それに触れているものだけ ``` ### どのトピックを読むか `culebra docs quick-guide`が凝縮パックです。構文・他言語から転移しない癖・ 言われなくても手が伸びる範囲の署名が、プロンプトに収まる1ファイルに 入っています。これは検索するものではなく、culebraを書く前に読むもの です。状態を持つ仕組み — `Scene`、`Canvas`、`Net`、`PEG`、`CodeGen` など — は署名だけでは動かし方が分からないので、一覧にせず名前だけを 挙げてあります。必要になった時にその章を表示してください。残りは プロンプト窓より大きく、そのために`-g`があります。 `culebra docs agent`はさらに短く、プログラムの外に出ていくことを 前提にした唯一のトピックです。コーディングエージェントが既に読んで いるファイルに追記する規則で、署名を推測せずここで引くようにさせます。 ``` culebra docs agent >> CLAUDE.md # Claude Code culebra docs agent >> .github/copilot-instructions.md # GitHub Copilot culebra docs agent >> AGENTS.md # Codex, Cursor ``` 追記先の一覧はstdoutではなくstderrに出るので、リダイレクトには markdownだけが入ります。`agent`も`llm`も横断検索には参加しません (どちらも他のトピックを凝縮したもので、同じAPIが二重に出ます)。 名前を指定すれば検索できます: `culebra docs agent -g …`。 `culebra init`はこの追記を代わりにやってくれます — 上記3ファイルの うちどれが既に存在するかを検出し(どれも無ければ`AGENTS.md`を新規作成)、 再実行するたびにブロックを最新に保ちます。`culebra docs agent`を直接 使うのは、生のmarkdownが欲しいときか、このリストに無い追記先を使う ときだけで十分です。 ## 6. 静的ファイルの配信 (`culebra serve`) `culebra serve [-p PORT] [-d DIR]`は1つのディレクトリを素のHTTPで配信 します。ルーティング・WebSocket・OpenSSLは関係なく、ファイル一式 (`culebra build`の成果物・docsのプレビュー・静的サイトなど)を `localhost`に置くだけの場面で`python3 -m http.server`の代わりになります。 ポートは既定`8000`、ディレクトリは既定でカレント。 ``` culebra serve # . を :8000 で配信 culebra serve -p 3000 -d site # site/ を :3000 で配信 ``` ルーティング・WebSocket・静的ファイルと同居するAPIが要るならstdlibの `Http.server()`名前空間を使う — [`stdlib.ja.md` §15](stdlib.ja.md#15-http) 参照。 --- ## 7. Playgroundへのリンク PlaygroundはCulebraをブラウザで実行します。何を開くかは全てURLで決まります。 リンクは互いに独立した3つの部分から成ります。テキストが何と並んで動くかを指す **プロジェクト**、**テキスト**そのもの、そして受け取った人が着く**ページ**です。 どれも省略できます。 ### プロジェクト | パラメータ | 開くもの | |---|---| | `?example=` | Examplesメニューでの正確なタイトルで指すカタログの例 | | `?src=<https URL>` | Webから取得する`.cul`ファイル | | `?args=<command line>` | プログラムに渡す引数。プロジェクト自身の引数を上書きする | `?example=`はPlayground自身の例をメニューの表記どおり(URLエンコードして) 名指しします。同伴ファイルも一緒に来ます。自分専用のディレクトリに置かれた例は そのディレクトリ全体を連れてくるので、importもデータファイルもcheckoutと同じに 解決されます。 `?src=`はGitHub上のファイルを、人がコピーする3つの綴りのいずれでも受け取ります。 ``` https://github.com/<owner>/<repo>/blob/<ref>/<path>.cul https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path>.cul https://cdn.jsdelivr.net/gh/<owner>/<repo>@<ref>/<path>.cul ``` そのファイルが置かれたディレクトリが再帰的に付いてきます。取得はjsDelivr経由です。 GitHub自身のホストはブラウザにディレクトリ一覧を返さないためです。500ファイルまたは 20 MBを超えるディレクトリはその旨のメッセージとともに拒否されるので、大きな リポジトリのルートに置かれたプログラムはまず自分のディレクトリへ移す必要があります。 それ以外のhttps URLは、周りに何も伴わない単一ファイルとして取得されます。 エントリのパスがそのプログラムの`Sys.script`であり、`import`もそれを基準に 解決されます。リンクされたプログラムはcheckoutで使うのと同じ式で自分のファイルを 見つけます。プロジェクトが無ければ、テキストは`main.cul`として単独で動きます。 ### テキスト `#code=<payload>`がソースそのものを運びます。ペイロードはUTF-8のソースを base64urlにしたもの(`+`と`/`の代わりに`-`と`_`、パディング無し)で、そのほうが 短くなるときはgzip圧縮されます。どちらの形式かを告げるパラメータはありません。 gzipのマジックバイト2つはCulebraのソースの先頭には現れ得ないので、ペイロードは 自己記述的で、デコーダはそれを読みます。上限は入口と出口の両方にあります。約100万文字を 超えるペイロードは読まずに拒否され、展開して4MBを超えるソースは展開の途中で打ち切られます。 gzipは約1000倍まで膨らむので、入口の上限だけでは数百MBを要求するリンクが通ってしまいます。 クエリ文字列ではなくフラグメントに載るのは、フラグメントがサーバまで届かないからです。 GitHub PagesのCDNは8 KBを少し超えたあたりでリクエスト行を拒否し、プログラム全体を 無圧縮で符号化するとそれを超えることがあります。`?code=`は古い綴りで、既に出回って いるリンクのためにフラグメントの次に読まれます。 プロジェクトとテキストの両方がある場合、テキストがプロジェクトのエントリソースを 置き換え、周辺のファイルは引き続きプロジェクトが供給します。編集された例はこの形で 共有されます。デコードできないペイロードはブラウザのコンソールに報告され、リンクは プロジェクトのソースにフォールバックします。どちらも無ければ、Playgroundは プロジェクトのエントリソースを、無ければ前回訪問時に保存された下書きを、それも 無ければHelloの例を開きます。 ### ページ 受け取った人が見るものは2つのパラメータで決まります。`?layout=`がページのどこまでを 組むか、`?view=`がどのペインを軸にするかです。 | パラメータ | 見えるもの | |---|---| | `?layout=` 無し | Playground全体 | | `?layout=editor` | エディタ・Run/Stop・ペイン。サイトのヘッダ、examplesのメニュー、引数欄、New/Shareボタンは出さない | | `?layout=view` | ペイン1つだけ | | `?view=output` | Outputペイン(既定) | | `?view=tui` | TUIペイン | | `?view=canvas` | Canvasペイン | | `?split=top` | ビューをソースの横ではなく下に置く | 組み合わせられます。後者が前者の残したものを名指しします。`?layout=view`と `?view=canvas`で周りに何も無いゲーム、`?view=tui`ならターミナルプログラムの 同じ形、`?layout=editor&split=top`はソースとその下の出力を、自前のブランディングを 持つページの中に置いた形です。`?embed=1`と`?embed=editor`は`?layout=editor`の、 `?embed=output`は`?layout=view`の古い綴りで、どれも読み続けます。 `?split=`はソースと出力の並べ方です。既定の横並びは幅を必要とし、`top`は出力を 下に置きます。ページの全幅と数百ピクセルの高さしか無いフレームが欲しいのは後者です。 720px未満ではどちらにしても縦に積まれます。分ける幅が無いためです。 描画するプログラムは始まると自分のペインへ移ります(TUIは代替スクリーンの エスケープで、Canvasは最初のフレームで)。`?view=`が効くのはそれより前と、 タブ列が無い`?layout=view`のページです。後者では`?view=`が唯一の指定手段になります。 `?run=1`はRunを待たず、エンジンの準備ができ次第プログラムを開始します。発火は 一度きりです。プロジェクトやテキストの読み込みに失敗したリンクは、代わりに 復帰したものを自動実行しません。 `?layout=view`のページにはツールバーが無いので、プログラムが動いていない間は ペインの上に再生ボタンが出ます。`?run=1`無しのリンクで訪問者が開始する手段であり、 終わったあと誰でも再生し直す手段でもあります。停止はありません。自分では終わらない プログラムはタブと一緒に終わります。 このページは空のペインではなくプログラムそのものから始まります。`?view=output`なら ソースを読み取り専用・ハイライト付きで表示し、最初の実行で出力に置き換わります。 `?view=canvas`と`?view=tui`ならプログラムの最初の画面で、これはそこまで実際に走らせて 止めることで得られます。Canvasのプログラムは最初の`Canvas.present()`で保留され、 TUIのプログラムは最初の画面の末尾にある`Term.read_key`で自分から止まります。再生ボタンは 2度目の実行を始めるのではなくその保留を解きます。クリックである以上それは音のロックも 解きます(ブラウザはページが一度クリックされるまで音声をロックするので、`?run=1`の ページは訪問者が触るまで無音です)。最初の画面に到達するまでプログラムは実際に走るので、 その途中で何かをするリンクは、訪問者が再生を押した時ではなくページを開いた時にそれをします。 WebAssembly JSPIの無いブラウザ(27より前のSafari)は、CanvasやTUIのプログラムを最初の画面で 保留することも入力を渡すこともできず、`?layout=view`のページには停止もありません。そこでは `?view=canvas`と`?view=tui`のページは`?run=1`の有無にかかわらず何も実行せず、`?view=output`と 同じようにソースを表示して、その上にブラウザに足りないものを示す一文を添えます。再生ボタンは出ません。 ### Shareボタン Shareはこれらの選択肢(`Layout`・`View`・`Split`・`On open`)を持つメニューを開き、 選んだとおりのリンクをコピーします。それ以外はページから決まります。プロジェクト、 プロジェクト自身のものと異なるときの引数、そして編集されたときのテキストです。 編集されていない例は数KBのフラグメントではなく短い名前で共有されます。既定値は 素のPlaygroundそのものなので、何も変えなければリンクは4つのどれも載せません。 Splitは効く場所でだけ出ます。ビューだけで組んだレイアウトには横に並べるソースが 無いので、その行は静かになり、リンクもそれを載せません。 Examplesのメニューをプレースホルダに戻すことは「このテキストはもうその例ではない」 という意思表示で、名前と一緒にプロジェクトも外れます。以後そのテキストは、例の同伴 ファイルも引数も伴わず`main.cul`として単独で動き、リンクも何も名指ししません。 テキスト自体は残ります。そこがShareの隣のNewボタンとの違いで、あちらはテキストも 消します。プロジェクトを名指ししないリンクは、 編集の有無にかかわらずテキストを載せます。そうしないと相手に何も届かないからです。 --- ## 8. エディタ支援 (`culebra lsp`) `culebra lsp`は [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP)のサーバです。エディタがこれを起動し、標準入出力で通信します。 ソースを読むだけで、プログラムは実行しません。 ``` culebra lsp # 標準入出力でLSPを話す ``` 提供する機能は次のとおりです。 - **診断** — `culebra lint`が報告する内容([§2](#2-lint-culebra-lint))を、 入力しながら表示します。編集したバッファは、入力が0.25秒止まったところで 解析し直します。保存したときはすぐに解析します。 - **整形** — 文書全体を`culebra fmt`と同じように整形します ([§3](#3-フォーマット-culebra-fmt))。パースできないバッファには触れません。 - **ホバー** — 文書の中で宣言した名前には、その宣言と推論した型を表示します (`parts: Array<String>`)。それ以外は、リファレンスに見出しがある名前について、 その項目を表示します。標準ライブラリの関数や定数(`Math.abs`、`Math.pi`)と、 グローバル関数(`type_of`)が対象です。値に対して呼ぶメソッド(`xs.size()`) には何も表示しません。どのメソッドになるかは値の型で決まるためです。 - **補完** — `value.`の後では、値の型から分かる範囲でメンバーを出します。 組み込み型のメソッド、クラスのインスタンスのメソッドとフィールド(`self`を 含む)、オブジェクトリテラルのキー、namespaceのメンバー、標準ライブラリの 関数が返すクラス(`Regex.compile(p).`)のメソッドが対象です。その後に、 UFCSで呼べる関数を続けます。名前だけを打っているときは、その位置で見える 名前を出し、続けてグローバルを出します。型はリテラル、戻り値の型、型注釈、 代入から求めます。関数を受け取る組み込みメソッドでは、その関数の引数を受け手の 要素型とし(`words.map(|w| w.`)、結果の型は関数が返すものから決めます。 型注釈のない引数は、文書の中の呼び出しが渡す値から型を求めます。ただし、その 関数がexportされている場合や、値として渡されている場合は求めません。importした 名前は、importしたモジュールの宣言から型を求めます。型が分からない値には、 UFCSで呼べる関数だけを出します。 - **定義へ移動・参照の検索・ハイライト** — 変数、引数、関数、クラス、enum、 import、エフェクト操作が対象です。名前の見え方は [`language.ja.md` §6](language.ja.md#6-変数とスコープ)の規則に従います。 ブロック、ループ本体、matchの腕はそれぞれ自分の名前を持ち、クロージャは 外側の関数の変数を、宣言の位置にかかわらず参照できます。直接の呼び出しに 書いたキーワード引数(`f(count: 2)`)は、その引数への参照として数えます。 importした名前`Alias`について`Alias.member`の定義へ移動すると、そのモジュールの トップレベルにある`member`の宣言を開きます。 - **アウトライン** — トップレベルの宣言を、クラス・enum・traitのメンバーと 一緒に並べます。 - **名前の変更** — 文書の中の出現をすべて書き換えます。オブジェクトの省略記法は キーを残し、`{sum}`は`{sum: total}`になります。書き換えきれない場合は、 サーバが変更を断ります。 - その名前がexportされている。importしているモジュールが追従しません。 - 同じ名前のメソッド呼び出し(`value.name(...)`)がある。UFCSでその関数に 届く可能性があります。 - 新しい名前がキーワードである。または、変更する名前を使っている場所で、 すでに別のものを指している。 - 文書が今の状態ではパースできない。 位置はプロトコルの既定に従います。行は0から数え、列はUTF-16のコード単位で 数えます。パースできない間は、最後にパースできた版をもとに、定義・参照・ ハイライト・アウトラインを返します。 ### 設定 `culebra init`([§4](#手早いセットアップ-culebra-init))は、ほかのエディタ統合と 一緒に、このサーバをVSCode・Neovim・Zedに組み込みます。 - **VSCode** — 同梱の拡張が`.cul`ファイルに対して`culebra lsp`を起動します。 **Format Document** と`editor.formatOnSave`もこのサーバを通ります。 - **Neovim 0.10以降** — 同梱の`ftplugin`が`.cul`のバッファごとに起動します。 `K`でホバーを表示し、`:lua vim.lsp.buf.format()`で整形します。止めるには `let g:culebra_lsp = 0`を設定します。 - **Zed** — 拡張がサーバを登録します。Zedは既定でこのサーバを使って整形します。 - **Vim** — Vimには組み込みのクライアントがありません。 [vim-lsp](https://github.com/prabirshrestha/vim-lsp)を使う場合は、 `vimrc`でサーバを登録します。 ```vim au User lsp_setup call lsp#register_server({ \ 'name': 'culebra', \ 'cmd': ['culebra', 'lsp'], \ 'allowlist': ['culebra'], \ }) ``` - **ほかのLSPクライアント** — `culebra`言語(拡張子`.cul`のファイル)に対して `culebra lsp`を起動します。