NonTerminal — 中に規則を抱える枝
🎓 発展コース です。前のほうで Symbol(共通の抽象)と Terminal(葉)を 見ましたね。いよいよ残りのもう一方の枝分かれ — 枝、NonTerminal です。
NonTerminal は Expr、Term、Factor のような 規則 です。
Terminal と決定的に違う点が一つあります。
Terminal は葉なので中が空っぽでしたね。
NonTerminal はその正反対 — 中に何かをたっぷり抱えています。
著者の悩み — 「規則は いくつもの方法 で作れるじゃないか」
私たちの例の最初の一行をもう一度見てみましょう。
Expr : Expr '+' Term | Term ;
Expr を作る方法が 二通り ですね。
Expr '+' Term であるか、それとも単なる Term であるか、どちらか一方です。
著者はここでこう判断したはずです。
「規則一つは いくつもの選択肢 で作られることがある。だとすると NonTerminal は自分の中にその 選択肢たちの束 を抱えていないといけないな」
そこで NonTerminal は alters というものを抱えます。
📍
NonTerminal : Symbol·…/RegularGrammar/NonTerminal.cs
public class NonTerminal : Symbol, IEnumerable<NonTerminalSingle>
{
private NonTerminalAlter alters = new(); // ← この規則の「選択肢たち」
public string Name { get; } // 規則の名前 (Expr, Term …)
public bool IsStartSymbol { get; } // この規則が構文解析の出発点か
}
Expr : Expr '+' Term | Term ; というこの一行が、まるごとこの alters の中に入るわけです。
言葉だけだと抽象的ですよね?
具体的にどんな形なのか を描いてみましょう。
Expr : Expr '+' Term | Term ; │ ▼ この一行が alters の中にこう収まります alters (NonTerminalAlter) ├─ 選択肢 0 : [ Expr ] · [ '+' ] · [ Term ] ← "Expr '+' Term" (順番どおり) └─ 選択肢 1 : [ Term ] ← "Term"
| が 選択肢を区切り、各選択肢の中では記号たちが 順番どおり に並びます。(この「順番」と「選択肢」を
コードでそれぞれどう表現するかが — 次の Concat 章からのテーマです。)
二つの付箋 — 名前と開始記号
規則には情報がもう二つくっつきます。
① Name — 規則の名前
Name — 規則の名前です。
Expr、Term のような名前です。(Symbol のアイデンティティは UniqueKey でしたね? Name はそれとは別物の 表示用 です。)
② IsStartSymbol — 構文解析の出発点か
IsStartSymbol — この規則が文法の 開始記号 かどうかを示します。
開始記号は 言語全体を作り出す 規則です — 構文解析木の根 であり、構文解析が終わったときに到達すべき記号です。(LR パーサは入力を 最初のトークンから 読み上げていき、最後にこの記号へと還元します。)
その開始記号だけにこの印が灯ります。
規則に生成規則を付ける — AddItem / SetItem
では私たちがコードで Expr に選択肢を 追加 するにはどうすればいいでしょうか?
NonTerminal が提供する AddItem / SetItem でです。
// NonTerminal.cs
public void AddItem(NonTerminal production, MeaningUnit meaningUnit = null) { ... }
public void SetItem(NonTerminal production, MeaningUnit meaningUnit = null) { ... }
AddItem— 既存の選択肢たちに もう一つ 乗せます。SetItem— 既存のものを まるごと消して 新しく敷き直します。
ここで一緒に受け取る MeaningUnit は「この規則が AST でどんな意味の単位になるか」なのですが — それは後で
別に扱います。
今は 「規則に選択肢を付ける入口が AddItem/SetItem なんだな」 まででじゅうぶんです。
巡回すると — 選択肢が一つひとつ飛び出してきます
NonTerminal は IEnumerable<NonTerminalSingle> です。
つまり foreach (var single in Expr) とすると、選択肢が一つずつ NonTerminalSingle として出てきます。
foreach (NonTerminalSingle single in Expr) // 0番目の選択肢、1番目の選択肢 …
{
// single = "Expr の N番目の選択肢"
}
この NonTerminalSingle が正確に何なのかは — すぐ次のテーマです。
一枚の絵
ここまでを絵にまとめるとこうなります。
NonTerminal "Expr"
├ Name = "Expr", IsStartSymbol = ?
└ alters : NonTerminalAlter ← 選択肢たちの束
├ (選択肢 0) Expr '+' Term
└ (選択肢 1) Term
foreach で巡回すると → NonTerminalSingle 0, NonTerminalSingle 1
📐 著者の設計ダイアグラム
- NonTerminal の子(選択肢)構造 — https://www.lucidchart.com/documents/edit/332a9afe-d053-4c13-ab2a-7110f25bff73/0
(著者本人の設計ノートなので、閲覧権限が必要なことがあります。)
ひと目で — NonTerminal の全体像
NonTerminal クラスの 全体の骨格 です。
ロジックは空にして 何があるか だけを見せます。(Terminal より
ずっと豊かですね — 中に選択肢たちを抱えるのですから。)
public class NonTerminal : Symbol, IEnumerable<NonTerminalSingle>, ICloneable
{
private NonTerminalAlter alters; // ← 選択肢たちの束 (核心)
// ── 情報 ────────────────────────────────
public string Name { get; } // 規則の名前
public bool IsStartSymbol { get; internal set; } // 構文解析の出発点か
public bool AutoGenerated { get; } // 数量子などで自動生成されたか
public bool IsInduceEpsilon { get; } // 選択肢の中に空のもの(ε)があるか
public int Count { get; } // 選択肢の個数
public NonTerminalSingle this[int index] { get; } // N番目の選択肢
// ── 生成 ────────────────────────────────
public NonTerminal(string name, bool bStartSymbol = false, bool autoGenerated = false);
// ── 選択肢を付ける ───────────────────────────
public void AddItem(NonTerminal production, MeaningUnit mu = null); // もう一つ乗せる
public void AddItem(NonTerminal production, uint priority, MeaningUnit mu = null);
public void AddItem(Terminal item, MeaningUnit mu = null);
public void SetItem(NonTerminal production, MeaningUnit mu = null); // まるごと差し替える
public void AddAsConcat(params Symbol[] symbols); // 一行(連接)を選択肢として
public void AddAsAlter(params Symbol[] symbols); // それぞれを別々の選択肢として
// ── 探索 / 置換 ─────────────────────────
public bool IsContain(Symbol item, ...);
public bool IsExistRefContent(NonTerminal target);
public void Replace(NonTerminal from, NonTerminal to, ...);
public HashSet<NonTerminal> ToNonTerminalSet();
// ── 巡回 / 表現 ─────────────────────────
// IEnumerable<NonTerminalSingle> 明示的実装 — public ではない、foreach でのみアクセス
IEnumerator<NonTerminalSingle> IEnumerable<NonTerminalSingle>.GetEnumerator(); // 選択肢を一つずつ → NonTerminalSingle
public override string ToEbnfString(bool bContainLHS = false);
public override string ToGrammarString();
public override string ToString(); // → Name
}
次の章
NonTerminal が 選択肢たちの束(alters) を抱えるということを見ました。
ところがその 選択肢一つ は、コードの中で正確にどんな形なのでしょうか?
Expr '+' Term のような 順番(連接) と、... | Term のような 選択(択一) — この二つの正体を
次の章で掘り下げます。