Context-free doesn't mean "no state". A parser has a lot of state to keep track of what grammatical rules are possible given the tokens it's seen so far and to predict what tokens will come next. Because there's no rule that says that two expressions can appear directly adjacent to each other it'll never even consider that a+b
could be the expressions a
and +b
side by side.
For example, let's say that we're using this rudimentary grammar:
expr → expr '+' unary_expr | unary_expr
unary_expr → '+' unary_expr | IDENT
(Notation: →
gives the rules a non-terminal can expand to and |
indicates alternate possibilities. '+'
is the plus token and IDENT
is any identifier token.)
Let's parse a+b
. Our parser's starting state will be:
1. expr → expr '+' unary_expr
^
2. expr → unary_expr
^
3. unary_expr → '+' unary_expr
^
4. unary_expr → IDENT
^
There are the rules it's considering at the start. It doesn't know which it's going to get, could be any of them. Notice that each production it's considering also includes a cursor, which I've marked with ^
carets above. That's where in the rule the parser is.
Okay, so now it sees the first IDENT
token. It updates its state to the following:
1. expr → expr '+' unary_expr
^
2. expr → unary_expr
^
3. unary_expr → IDENT
^
Now there are three rules that it's considering. Notice that the cursor has moved along to the right in each of them.
If the first rule is correct then it has just seen an expression and is expecting a '+'
next. Alternatively, maybe the second rule is the correct one and a
is just a unary expression. In that case it expects no more tokens to follow. The parser doesn't know which it's going to be so it's considering both.
You'll see that if the next token is a '+'
then it must be a binary plus. Why? Because the first rule is the only rule that anticipates a '+'
token next.
For '+'
to be interpreted as a unary plus the parser would have to have this rule in its active state, with the cursor before the '+'
:
unary_expr → '+' unary_expr
^
And you can see that it doesn't.
If context-free doesn't mean stateless, what does it mean, then? What "context" are we "free" from?
Context-free is a restriction on what rules the grammar can contain. The opposite is context-sensitive, where where productions can vary based on their surroundings. Context-sensitive grammars are more powerful than context-free grammars but they are much more difficult to parse—even for humans! Language theoreticians figured out early on that context-free grammars occupy a sweet spot of being powerful enough to be expressive without being overwhelmingly complex to reason about.
For more details see:
Context-free grammars versus context-sensitive grammars?
A context-free grammar (CFG) is a grammar where (as you noted) each production has the form A → w, where A is a nonterminal and w is a string of terminals and nonterminals. Informally, a CFG is a grammar where any nonterminal can be expanded out to any of its productions at any point. The language of a grammar is the set of strings of terminals that can be derived from the start symbol.
A context-sensitive grammar (CSG) is a grammar where each production has the form wAx → wyx, where w and x are strings of terminals and nonterminals and y is also a string of terminals. In other words, the productions give rules saying "if you see A in a given context, you may replace A by the string y." It's an unfortunate that these grammars are called "context-sensitive grammars" because it means that "context-free" and "context-sensitive" are not opposites, and it means that there are certain classes of grammars that arguably take a lot of contextual information into account but aren't formally considered to be context-sensitive.