Table of Contents

FIRST ยท Computation Rules

๐ŸŽ“ This is the Advanced track ยท Theory.
In the previous page, FIRST ยท Definition & Derivation, we grabbed the definition and even derived it straight from the definition.
But we ran into the wall where, with recursion, the derivation grows infinitely long.

So this page is โ€” a computation rule that pulls out the same FIRST without expanding the derivation directly.
(Recursion gets handled gracefully too.)
How that rule is implemented in code is over at โ†’ FIRST ยท Implementation.

Don't try to take it all in at once.
We'll go one step at a time, starting from the easy bits.

First โ€” let's lay out the whole cast and begin

Before working out FIRST, let's see what's in this grammar at a glance.
A grammar's symbols come in just two kinds.

  • terminal โ€” a token that actually shows up in the input
  • nonterminal โ€” a rule name

Splitting the two apart in our example grammar, it looks like this.

   terminal list    :   (    )    +    *    id        โ† 5 of them
   nonterminal list :   Expr    Term    Factor        โ† 3 of them

FIRST is worked out for each one of these symbols.
So our job is clear โ€” fill in the FIRST of 5 terminals + 3 nonterminals, 8 in all.
And here's the good news โ€” the terminal side is almost free. Let's start there.

FIRST of a terminal โ€” every one is itself (done in one shot)

A terminal starts with itself.
Of course it does โ€” + starts with +.

How to read it first. FIRST( '(' ) = { '(' } reads as โ€” "the FIRST set of the terminal ( is the single (." ({ } is a set, and what's inside it are the elements.)

So the 5 terminals, all in a row โ€”

FIRST( '(' ) = { '(' }   โ† the FIRST of terminal '(' is itself
FIRST( '+' ) = { '+' }   โ† the FIRST of terminal '+' is itself
FIRST( ')' ) = { ')' }   โ† the FIRST of terminal ')' is itself
FIRST( '*' ) = { '*' }   โ† the FIRST of terminal '*' is itself
FIRST( id  ) = { id  }   โ† the FIRST of terminal id is itself

Summed up in one line โ€” FIRST(terminal a) = { a } (a terminal's FIRST is always itself).
5 terminals, and that's done. We filled in 5 of the 8 for free. ๐Ÿ™‚

FIRST of a nonterminal โ€” the big picture first

Now the real subject, the three nonterminals (Expr Term Factor).
Before that, let me grab the big picture first.

๐Ÿ“Ž Quick aside, one term. In Factor : '(' Expr ')' | id, each individual alternative split by | is called a production.
It's "a single line that makes a nonterminal," like Factor โ†’ id. (Details in Single.)
From here on we'll use this word.

The FIRST of one nonterminal is worked out like this โ€” take the FIRST of each of that nonterminal's productions, and combine them all (union).

   FIRST(Factor) = FIRST(Factor's production 1) โˆช FIRST(Factor's production 2) โˆช โ€ฆ

So the question we really have to solve narrows down to one โ€” how do you work out the FIRST of a single production?

The answer is surprisingly simple.
It depends on what that production starts with, and there are exactly three cases.

  1. Case โ‘  โ€” when it starts with a terminal
  2. Case โ‘ก โ€” when it starts with a nonterminal
  3. Case โ‘ข โ€” when the front can disappear (ฮต)

Let's look at them one by one, starting from the easy one.

Case โ‘  โ€” when a production starts with a terminal

This is the easiest case. In fact, it's just using what we already saw above.

If the front is a terminal, that production's FIRST is exactly that terminal.
Just above, we said "a terminal's FIRST is itself," right? This is the exact same story โ€” the front terminal is the answer.
(It doesn't matter if there are more symbols after the production. If the front is a terminal, that's the first terminal, so it ends right there.)

For example, Factor's two productions are these.

   Factor โ†’ id            front is terminal id   โ†’   FIRST = { id }
   Factor โ†’ '(' Expr ')'  front is terminal (    โ†’   FIRST = { '(' }

Even though '(' Expr ')' has Expr ')' after it, it ends right at the front (.

Since Factor has only these two productions, combining them completes it right away.

   FIRST(Factor) = { '(' } โˆช { id } = { '(', id }

First nonterminal done! ๐Ÿ™‚

Case โ‘ก โ€” when a production starts with a nonterminal

This time the front isn't a terminal but another nonterminal. Our grammar's Expr โ†’ Term is exactly that โ€” the front is the nonterminal Term.

So what's this production's FIRST? โ€” we bring the front Term's FIRST over as is. That is, FIRST(Expr) = FIRST(Term).

Why is that?
Back to the definition โ€” FIRST is "the terminal that appears first when you derive." When you derive Expr โ†’ Term, Term takes the front spot, so the front terminal that comes out when you expand all the way is also ultimately decided by Term.

Let's see it directly with a derivation. Expanding Expr โ†’ Term all the way โ€”

Expr  โ‡’  Term  โ‡’  Factor  โ‡’  id            front terminal = id
Expr  โ‡’  Term  โ‡’  Factor  โ‡’  ( Expr )      front terminal = (

๐ŸŽจ purple = nonterminal (ExprยทTermยทFactor), teal = terminal (idยท(ยท)).

The front spot is held by Term (โ†’ Factor โ†’ โ€ฆ) from start to finish, right?
So the terminals that come out at the front, id ยท (, are exactly the first terminals Term produces โ€” that is, FIRST(Term) itself.
So FIRST(Expr) = FIRST(Term).
(Expr's other production, Expr '+' Term, is the left recursion we'll see shortly, so it adds nothing new, and this equality holds exactly.)

๐Ÿ”– Generalized in one line: if the front of a production is a nonterminal โ€” bring that nonterminal's FIRST over as is. (Except, if that nonterminal is itself, you get snagged once โ€” right below.)

But โ€” what if that nonterminal is itself? (left recursion)

Here you get snagged once.
Look at the first production of Term : Term '*' Factor | Factor.

   Term โ†’ Term '*' Factor     front is again Term โ€” that's itself?!

Trying to work out FIRST(Term), the front nonterminal is again Term.
That is, to work out FIRST(Term) you need FIRST(Term) โ€” a chicken-and-egg situation.
As is, this doesn't solve in one go. But this kind of direct left recursion (where it bites itself directly) is actually easy.

Easy recursion โ€” just drop that rule

Just ask what Term can start with. Term has two rules, and they differ in what the front becomes.

   โ‘  Term โ†’ Factor            front is Factor (settled!)
   โ‘ก Term โ†’ Term '*' Factor   front is Term again (no progress)

โ‘ก leaves a Term at the front, so it never settles the first symbol (Term just comes back as Term). So the only rule that settles the front is โ‘ , and it makes the front a Factor. So Term always starts with a Factor, which means FIRST(Term) = FIRST(Factor) = { '(', id }.

Expr (Expr : Expr '+' Term | Term) is the same direct left recursion, so by the same reasoning it's { '(', id }.

But this "just drop the in-place rule" shortcut only works for direct left recursion like Term โ€” where it bites itself directly. When nonterminals bite each other in a loop (indirect left recursion), it's a different story.

   A โ†’ B โ€ฆ
   B โ†’ A โ€ฆ

A starts with B, and that B starts with A again, so there's no directly self-biting "in-place rule" anywhere to drop. To handle even cases like this all at once, the engine doesn't distinguish direct from indirect โ€” it handles every nonterminal the same way, by starting from the empty set and repeating until nothing more grows.

For direct left recursion like Term, this repetition finishes in a single round, so it's practically free. Where the repetition really shows its power is when nonterminals tangle up like this โ€” and that comes up naturally in the next FOLLOW chapter, on this very expr grammar.

๐Ÿ’ก The wall of recursion where the derivation kept growing infinitely long on the previous page (Definition & Derivation) โ€” getting over that wall is exactly this "repetition."
Instead of expanding all the way, we grow the set little by little and stop when it stops changing.

Case โ‘ข โ€” when the front nonterminal can disappear (ฮต)

The last case.
In Case โ‘ก we said "bring the front nonterminal Y's FIRST over," right?
But if that Y can also disappear into nothing (ฮต), there's one more thing to take care of.
(When a nonterminal can derive even the empty string, we call it nullable.)

Why do we have to look at the next symbol too?
Again, it's the definition โ€” FIRST is "the terminal that appears first when you derive."
But if the front Y disappears into ฮต, the front of the derivation result is taken not by Y but by the very next symbol.
Then the first terminal that next symbol derives can come to the front too.
So we have to add the next symbol's FIRST too to Y's FIRST to get it right.
This rule of "if the front can disappear, move on to the next symbol and combine" is called โŠ• (ring-sum).

   A โŠ• B =  A              (if A can't disappear โ†’ it ends there)
            (A-ฮต) โˆช B      (if A can disappear โ†’ drop ฮต, and add B too)

We need to show ฮต here, but our expr grammar has no nullable, so instead of expr we use a small grammar. Here's the grammar we'll use for examples in this chapter:

S โ†’ A B
A โ†’ a | ฮต
B โ†’ b | ฮต

A grammar where A and B can each disappear into ฮต (nullable).

Let's start with A and B's FIRST. Both have an ฮต branch, so ฮต gets in.

   FIRST(A) = { a, ฮต }
   FIRST(B) = { b, ฮต }

Now for FIRST(S). In S โ†’ A B, since the front A can disappear, โŠ• doesn't stop at the first slot โ€” it moves on to the next slot B.

   FIRST(S) = FIRST(A) โŠ• FIRST(B) = ( { a, ฮต } โˆ’ ฮต ) โˆช { b, ฮต } = { a, b, ฮต }

If A didn't disappear it would end at { a }, but since A can become ฮต, B's first symbol b can come to the front too. And if B disappears as well, S becomes empty entirely, so ฮต gets in too. This is โŠ• actually at work.

Our expr grammar (Expr/Term/Factor) has no nullable, so there โŠ• always stops right at the front symbol. Still, the rule has to include this ฮต handling to be correct.

Summary โ€” the three cases are really one formula

We split it into Cases โ‘ โ‘กโ‘ข above, but in fact these three combine into a single formula.

A production is, after all, a sequence of symbols (like Term '*' Factor).
Its FIRST is โ€” the FIRST of the component symbols, โŠ• (ring-summed) in order, that's all.

   FIRST(Xโ‚ Xโ‚‚ โ€ฆ Xโ‚™) = FIRST(Xโ‚) โŠ• FIRST(Xโ‚‚) โŠ• โ€ฆ โŠ• FIRST(Xโ‚™)

So the three cases from before are just a difference in where this โŠ• stops.

  • Case โ‘  : the first symbol is a terminal. A terminal can't be ฮต, so โŠ• stops at the first slot and becomes { that terminal }.
  • Case โ‘ก : the first symbol is a nonterminal (that can't be ฮต). โŠ• stops at its FIRST and becomes FIRST(that nonterminal).
  • Case โ‘ข : the first symbol can be ฮต. So โŠ• moves on to the next slot and keeps combining.

For example, โŠ•-ing Term '*' Factor directly looks like this.

   FIRST(Term '*' Factor) = FIRST(Term) โŠ• FIRST('*') โŠ• FIRST(Factor)
                          = FIRST(Term)              โ† Term can't be ฮต, so it stops at the first slot

The code is exactly this too โ€” it RingSum(โŠ•)s the symbols of a production (Concat) in order, and stops when there's no more ฮต to look at.

// FirstFollowAnalyzer [First].cs
public TerminalSet FirstSet(NonTerminalConcat singleNT, ...)
{
    TerminalSet result = new TerminalSet();

    foreach (var symbol in singleNT)                        // the production's symbols in order
    {
        result = result.RingSum(FirstSet(symbol, seenNT));  // โŠ• one slot
        if (!result.IsNullAble) break;                      // no more ฮต to look at โ†’ stop
    }

    return result;
}

Verification โ€” run this rule on our grammar, and all three come out the same.

   FIRST(Factor) = FIRST(Term) = FIRST(Expr) = { '(', id }

Exactly the same as the answer we worked out by hand on the Definition & Derivation page. โœ“


Finally โ€” take the โŠ• we've seen so far (one production), wrap one more layer of alternative (|) combining โˆช around it, and this is the whole of FIRST:

   FIRST(A) = โ‹ƒ ( FIRST(Xโ‚) โŠ• FIRST(Xโ‚‚) โŠ• โ€ฆ )
  • outer โ‹ƒ : combines A's several productions (the | alternatives). ย (= a nonterminal's FIRST = the union of all its productions' FIRST)
  • inner โŠ• : joins one production (a symbol sequence), but moves to the next slot if the front disappears. ย (= Cases โ‘ โ‘กโ‘ข)
  • bottom FIRST(terminal a) = { a }.

In this one line โ€” Cases โ‘ โ‘กโ‘ข and alternative combining are all in there. FIRST really is just this. ๐ŸŽฏ

Next โ€” this rule, in code

The three cases we just saw โ€” starts with terminal ยท starts with nonterminal ยท ฮต โ€” and "repeat until nothing changes," are baked into the FirstFollowAnalyzer code almost line for line.
Let's look on.

๐Ÿ‘‰ FIRST ยท Implementation (code)


๐Ÿ‘ˆ Previous: FIRST ยท Definition & Derivation