第1回: なぜ構文エラーは生まれるのか

シリーズ「構文エラーのない世界へ」 — 目次に戻る


プログラムの二重生活

プログラムには二つの顔がある。

一つはテキストとしての顔。エディタに表示され、キーボードで入力し、ファイルに保存される文字の並び。

let total = prices.reduce((sum, p) => sum + p, 0)

もう一つは構造としての顔。コンパイラやインタプリタが理解する、木のような形をしたデータ。

        let
       /   \
    total   .reduce
            /    \
        prices   (callback, 0)
                  /
              (sum, p) => sum + p

この木を AST(Abstract Syntax Tree: 抽象構文木) と呼ぶ。プログラミング言語の処理系がプログラムを実行するとき、相手にしているのはテキストではなく、この木だ。

プログラマーはテキストを書く。コンパイラは木を読む。この間を繋ぐのがパーサーだ。

テキスト → [パーサー] → AST → [コンパイラ] → 実行

パーサーは厳格な翻訳者

パーサーの仕事は、文字の並びを木構造に変換すること。しかしパーサーには厳格なルールがある。変換できるのは、文法に従った「正しい」文字列だけだ。

一文字でもルールに合わないと、パーサーは構文エラーを返す。これは「翻訳できません」という意味だ。

ここが問題の核心になる。

途中状態は「間違い」ではない

プログラムを書いている最中のことを思い出してほしい。

let x =

ここで手を止めて考えている。値に何を入れるか。この文字列は「不完全」だが、間違っているわけではない。まだ考えている途中なだけだ。

しかしパーサーにとって、この文字列は「変換できない」。結果として構文エラーが発生する。構文エラーが発生すると、型チェックも、入力補完も、リファクタリングも、すべてが止まる

もう少し具体的に見てみよう。let x = 1 + 2 と入力する過程を追う。

l           → 構文エラー
le          → 構文エラー
let         → 構文エラー(まだ不完全)
let x       → 構文エラー
let x =     → 構文エラー
let x = 1   → パース成功 ✓
let x = 1 + → 構文エラー(演算子の右辺がない)
let x = 1 + 2 → パース成功 ✓

8ステップ中、パースに成功するのは2ステップだけ。書いている時間の75%で、エディタはプログラムの構造を把握できていない。

これが「構文エラーが生まれる」構造的な理由だ。テキストという表現とASTという表現の間にパーサーがいて、パーサーは途中状態を受け付けない。

エラー回復という応急処置

現代のエディタ(VSCode、IntelliJ等)はこの問題にかなり対処している。エラー回復(error recovery) と呼ばれる技術で、パースが失敗しても「なんとかそれらしいAST」を推測して構築する。tree-sitter のようなインクリメンタルパーサーは、この技術を高度に発展させたものだ。

しかしこれは本質的に「推測」だ。推測が外れることもあるし、推測の結果としてのASTに対する型チェックの結果は信頼性が下がる。一つの構文エラーが原因で、全く関係ない場所に大量のエラーが表示される経験は、多くのプログラマーが持っているだろう。

本当の問題

ここまでの議論をまとめると、問題は三層になっている。

第一層: テキストとASTの不一致 プログラマーはテキストで考え、テキストで入力する。しかし言語処理系が必要としているのはAST。この変換は、入力の途中段階では失敗する。

第二層: パーサーの全か無か パーサーは「完全に正しい文字列」か「エラー」の二択しかない。エラー回復は応急処置であり、正確さの保証がない。

第三層: エラーの連鎖 パースが失敗すると、それに依存する型チェック、入力補完、リファクタリングといったエディタ機能がすべて劣化する。一つの不完全さが全体に波及する。

これは特定のエディタやパーサーの実装の問題ではない。「テキストを書いてパーサーに渡す」というモデルそのものに内在する問題だ。

では、どうすればいいのか?

発想を転換する。

テキストからASTに変換するのではなく、ASTそのものを直接編集する。画面に表示されるテキスト風の見た目は、ASTを人間向けに「表示」した結果にすぎない。パーサーは中心的な役割から退き、テキスト入力を補助する脇役になる。

この発想を Projectional Editing(射影編集)と呼ぶ。

次回は、この Projectional Editing がどういう仕組みで、何を解決し、何が難しいのかを見ていく。


次回: 第2回: Projectional Editing — もう一つのやり方