Table of Contents

lookahead(LR(1) 項目)

SLR の章で私たちは、A → α • に出会ったら、どんな次の文字で reduce するのか?」という問いを FOLLOW(A) で答えました。ところが見かけの衝突で見たように — FOLLOW(A)文法全体をひとまとめにしたものなので、いまこの場所には広すぎたのです。

そこでもう一歩進みます — 項目の一つ一つに、その場所にぴったり合う次の文字をつけよう。 この「項目につく次の文字」がまさに lookahead です。

この章では lookahead が正確に何で、どうやって求めるのかを — もれなく解いていきます。(次に来る CLR・LALR が、すべてこの lookahead の上に立っています。)


0. 記号の約束から — 混乱しないように

公式には α · β · t のような文字が出てきます。始める前にこれが何の文字なのかをはっきりさせておきます。(これをやらないとすぐ混乱します。)

これまで例題の a b c d x y は**文法に本当に埋め込まれた文字(終端記号)でしたね。ところが公式を書くときは — 「どの規則、どの文字でも」を指す空欄(プレースホルダー)**が必要です。

記号 どんな空欄 入りうる例
A B 非終端記号一つ(規則の名前) 例題の A · B · S
α β 記号の並び — 終端記号・非終端記号が0個以上並んだもの 空の並び · c · A c · a A
t 終端記号一つlookahead の場所 c · d · または入力の終わり $

⚠️ 表記の注意 — lookahead は a ではなく t で書きます。 教科書では lookahead の場所をふつう a で書きます。ところが私たちの例題には本物の文字 a(例: S → a A c)があるので、a本物の文字lookahead の場所の二つの意味で重なってしまいます。
そこでこのマニュアルでは lookahead の場所を t(terminal = 終端記号)書きます。t は特定の文字ではなく*「ここに来る次の終端記号一つ」*という空欄です — そこに実際に何が入るのかは公式で求めるものです。

要点だけ: α β t = 空欄、   a b c … = 本物の文字。


1. LR(1) 項目 — 項目に lookahead をつける

閉包の章の項目は規則 + ドットでした — 例: A → α • β(ドットの α はすでに読んだ、ドットの後ろ β はまだ)。

ここに **lookahead 一文字(t)**をつけたのが LR(1) 項目です。

[ A → α  β , t ]

後ろについた t の読み方はこうです:

A → α β を最後まで読んで **A にまとめた直後*、すぐ次の文字が t なら** — そのまとめは正しい。」*

つまり tこの規則をまとめてもよい次の文字です。(だから「先読み(lookahead)」と呼びます。)

さて — 「なぜ lookahead が t なのか?」 t文字の名前ではなく、**「まとめたあとに来る次の文字が入る場所」**という空欄です。その場所に実際にどんな文字が入るのかを求めるのが — まさに次の §2 です。

💡 SLR との比較: SLR は「A という規則には FOLLOW(A)」のように非終端記号の単位で lookahead をまとめていました。LR(1) 項目は項目の単位 — 同じ A → b • でもどの状態にいるかによって違う t を持つことができます。この細やかさが次の章たちの核心です。


2. その lookahead、どうやって求めるのか — 閉包で FIRST から

項目は閉包で増えていきます(閉包の計算法) — ドットの後ろに非終端記号があれば、その規則たちを広げることでしたね。LR(1) では、広げながら新しい項目の lookahead まで決めてあげます。

広げる状況はこうです。すでにこんな項目があるとしましょう:

[ A → α  B β , t ]

ドットの後ろに非終端記号 B がありますね(その後ろには β、そしてこの項目の lookahead t)。閉包は B → γ の規則たちを新しく入れます:

[ B γ , ? ]      ← この新しい項目の lookahead の場所には何を入れる?

考えてみましょう — B を全部まとめたら、そのすぐ後ろには何が来ますか?

[ A → α  B β , t ]

もとの項目で B の後ろにあった β です。だから B の lookahead は*β の最初の文字*たち(FIRST(β))になりますね。

ところがもし β が空か、まるごと消えうる場合(つまり B の後ろに事実上何もない場合)、そのときは B の次に来るのはもとの項目の lookahead t です。

新しい lookahead = t

なぜ t になるのでしょう? もとの項目は [ A → α • B β , t ] ですが、β が消えると実質 [ A → α • B , t ] になり、この項目どおり A を展開すると Bいちばん後ろに来ます。B をまとめると A もすぐ終わります。すると B の次に来るのは*A の次に来るもの*そのもので、それが何かはこの項目がすでに教えてくれています — lookahead t ですね。だから B はその t をそのまま引き継ぎます。

この二つの場合を一つの式にまとめたのが —

新しい lookahead = FIRST( β t )

FIRST(β t) = β の FIRST を集めつつ、β がすべて消えうるなら t まで含める — FIRST の定義そのままです。)

これが lookahead を求める公式のすべてです。新しい計算ではなく、すでに学んだ FIRST を「ドットの後ろの β」に適用するだけです。


3. 手で一度 — 本物の文字で

空欄(α β t)ではなく本物の文字でやってみましょう。小さな文法:

S( A )
An

( ) n は本物の終端記号です。n は数字や名前のようなトークンだと思ってください。)

始めて ( を読むと、こんな項目があります(いちばん外側は入力の終わり $ で閉じるので lookahead が $):

[ S(  A ) , $ ]

ドットの後ろが非終端記号 A ですね。広げて A → n を新しく入れる番です — 公式 FIRST(β t) に代入します:

  • ドットの後ろの非終端記号 B = A
  • A の次に残った β = )
  • この項目の lookahead t = $
  • → 新しい lookahead = FIRST( ) $ ) — ところが先頭の ) は終端記号なので消えられず、FIRST はその後ろの $ までは届きません。だから $ は外れて FIRST( ) ) = { ) }   () は終端記号なので自分自身)

そこで新しい項目は:

[ A n , ) ]

n を読むとドットが進んで — [ A → n • , ) ]
読み方: A → n をまとめた直後に ) が来たら正しい。」 — 当然ですね、( n )n の後ろには ) が来るのですから。

β が空の場合も一度見てみましょう。 もし規則が S → ( A ) ではなく単に S → A だったら?[ S → • A , $ ]A を広げるとき — A の後ろに残ったものがありませんβ が空)。すると公式が FIRST( $ ) = { $ }、つまり親の lookahead $ をそのまま引き継いで [ A → • n , $ ] になります。

🔖 一行まとめ — lookahead は*項目ごとにつく「まとめてもよい次の終端記号」*で、閉包では FIRST(β t) で求めます。(ドットの後ろの β の FIRST、β が消えれば親 t を引き継ぐ。)


次へ

これで lookahead が何で、どうやって出てくるのかが分かりました。これを状態を作るときから最初からつけて表を埋めると — それが CLR です。行ってみましょう。

👉 構文解析表 · CLR — しくみ