Table of Contents

NonTerminal — 中に規則を抱える枝

🎓 発展コース です。前のほうで Symbol(共通の抽象)と Terminal(葉)を 見ましたね。いよいよ残りのもう一方の枝分かれ — 枝、NonTerminal です。

NonTerminalExprTermFactor のような 規則 です。

Terminal と決定的に違う点が一つあります。

Terminal は葉なので中が空っぽでしたね。
NonTerminal はその正反対 — 中に何かをたっぷり抱えています。

著者の悩み — 「規則は いくつもの方法 で作れるじゃないか」

私たちの例の最初の一行をもう一度見てみましょう。

Expr : Expr '+' Term | Term ;

Expr を作る方法が 二通り ですね。
Expr '+' Term であるか、それとも単なる Term であるか、どちらか一方です。

著者はここでこう判断したはずです。

「規則一つは いくつもの選択肢 で作られることがある。だとすると NonTerminal は自分の中にその 選択肢たちの束 を抱えていないといけないな」

そこで NonTerminalalters というものを抱えます。

📍 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 — 規則の名前です。
ExprTerm のような名前です。(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 なんだな」 まででじゅうぶんです。

巡回すると — 選択肢が一つひとつ飛び出してきます

NonTerminalIEnumerable<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 の全体像

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 のような 選択(択一) — この二つの正体を 次の章で掘り下げます。

👉 Concat — 順番(連接)