User Tools

Site Tools


choice_points

Choice Points and Lookahead

A choice point in a JavaCC grammar is a juncture where the parser must decide between two or more expansions. There are four kinds of choice point:

  • An optional expansion, i.e. the expansion inside a zero or one construct, which is written ( Foo )? or alternatively as [ Foo ]
  • A repeated expansion that is possibly empty, a.k.a. zero or more which is written as ( Foo )*
  • A repeated expansion that cannot be empty, a.k.a. one or more which is written as ( Foo )+
  • The expansions within a choice construct, i.e. n (two or more) expansions, separated by the “|” character, i.e. Foo | Bar | Baz

(Perhaps needless to say, all of these constructs arbitrarily combine and nest within one another to an arbitrary level of complexity.)

I think the simplest way to think about the above cases (for most people, anyway) is just in terms of their analogues in a procedural programming language. The first three cases are effectively binary choices, a choice between entering the expansion inside the parentheses and jumping directly to what follows it. In all of these cases, the expansion within the parentheses is a choice point. The last case is a choice between n options and each of those n sub-expansions is a choice point in the grammar.

Zero Or One

A zero-or-one is a single (non-looping) choice. If the enclosed expansion matches, we enter it, and if not, we jump directly to whatever follows it. So, if we write:

    ( inner_expansion )? following_expansion

This is quite analogous to a simple if statement in a procedural language. In pseudo-code, something like:

    if (inner_expansion_matches()) inner_expansion();
    following_expansion();

Zero Or More

A zero or more is basically the same as a zero or one except that it is not a one-time choice, but a loop. So, the following:

    ( inner_expansion )* following_expansion

is a loop that, on each iteration, chooses between the inner_expansion and the following_expansion. As long as it chooses the first one, it stays in the loop, but once it opts for the following_expansion, it breaks out. In pseudo-code, that looks like:

    while (inner_expansion_matches()) inner_expansion();
    following_expansion();

One or More

A one or more is like a zero or more loop except that the first iteration is obligatory. Thus:

   ( loop_expansion)+ following_expansion

can be considered as just a more terse way of writing:

   loop_expansion ( loop_expansion )* following_expansion

So, one should bear in mind that a one or more loop is a little bit tricky because, on the first iteration, it is not actually a choice point! But, then again, the difference between zero or more and one or more is exactly the same as the difference between a while loop and a do-while loop in Java or similar languages. So, in pseudo-code, the one or more would be:

    do {inner_expansion();} while (inner_expansion_matches());
    following_expansion();

which, again, is just an abbreviated way of writing:

     inner_expansion();
     while (inner_expansion_matches()) inner_expansion();
     following_expansion();

So, again, we see that the first iteration is actually executed unconditionally.

The Choice Construct

This construct, something like expansion1 | expansion2 |…expansionN | … final_expansion might as well be thought of as an if-elseif-else construct. Again, in pseudo-code:

    if (expansion1matches()) 
      expansion1(); 
    else if (expansion2matches())
      expansion2();
    ....
    else
      final_expansion();

We test the conditions in sequence until one is satisfied and then we enter that expansion and that's that.

Well, all the above kind of begs the question of how we decide when an expansion matches the current input.

In the default case, this has a very simple answer:

The Default Choice Resolution Algorithm

The default choice resolution algorithm for whether we enter an expansion at a choice point is just to check ahead exactly one token and if that token potentially starts the expansion, then that is what we choose!

Another, more theoretical way of expressing this is:

The default assumption of a parser generated by JavaCC is that the grammar is LL(1).

(NB. I use this sort of terminology to demystify things. There is no need to be intimidated by this sort of thing. The above is just a fancy way of saying that looking ahead one token is enough to resolve any choices at any juncture in the grammar.)

Now, to be clear, looking ahead one token might be sufficient or not in any given spot, but if we don't specify any extra information, that is the default assumption. Another fancy, theoretical way of expressing this default would be:

We enter an expansion at a choice point if the next token is in that expansion's first set.

Again, no need to be intimidated by the lingo. An expansion's first set is simply the set of tokens with which an expansion can begin. Depending how your brain is wired, it might be easier to think about it the other way round. Like so:

If the next token is not in an expansion's first set, the expansion cannot possibly be matched, so we skip ahead to the following choice.

Now, regardless, it may be the case that more than one of the expansions at a given choice point matches this condition. Well, in that case, we have a secondary rule: the first one gets it. And this is really no different, by the way, from how if-elseif-else in a procedural programming language works.

If you write:

   if (x > 0) doSomething();
   else if (x > 1) doSomethingElse();
   else somethingElseEntirely();

then clearly the second choice, doSomethingElse() is never entered, because if the condition x>1 is satisfied, then x>0 would also have been satisfied, so we would have already entered doSomething() and subsequently jumped out of the if-else construct! Or, another way of putting this is that if x is 2, say, the first and second conditions match, but the first one gets it!

A funny thing about all this is that, in the context of procedural programming languages, nobody considers this to be a big deal, or to be confusing. However, the legacy JavaCC tool does make a big deal out of this, always emitting annoying warnings in analogous situations. JavaCC 21 does not bother to do so, at least, as I write these lines. At some later point, some checks will probably be put in place for emitting warnings when code is clearly unreachable, since that could be helpful in some spots. But it is not a high priority either. This was actually quite annoying in legacy JavaCC, because most of the time, the warnings emitted were complete spurious!

In any case, let's move to a concrete example. Suppose we have a choice construct like:

    "foo" "bar" ...
    |
    "foo" "baz" ...
    |
    "bar" "bat" ...

then yes, the second choice is clearly unreachable. Given that our default choice resolution algorithm is to look ahead exactly one token, if the next token is “foo”, we always enter the first choice and never the second one.

So, this leads us naturally to the next question, which is:

If the default resolution algorithm is not sufficient, (i.e. expressing things the egghead academic way, the grammar is not LL(1) at that juncture) how do we override that default and get the behavior we actually want?

The answer is that we do this by defining some sort of lookahead. (Note that lookahead is legacy JavaCC terminology but is frequently inaccurate. Really, we're talking about defining some sort of condition or predicate that overrides the default resolution algorithm, and that could well involve looking ahead more than the default of 1 token, or it could involve something else.) Probably the term predicate is more accurate, but that is a bit of a fancy word. Maybe just condition is better, but I may well use the term lookahead frequently in these spots because, yes, resolving the choice very often does involve scanning ahead more than one token. (But not always.)

Numerical Lookahead

In the case given above, where we have that unreachable second expansion, the simplest solution would simply be to specify that we should scan ahead two tokens on the first expansion. So we could write the above choice as:

    SCAN 2 "foo" "bar" ...
    |
    "foo" "baz" ...
    |
    "bar" "bat" ...

(Note that I use the newer SCAN statement here. In legacy JavaCC this would be the more verbose: LOOKAHEAD(2) “foo” “bar” etc )

Regardless, once we specify two tokens of lookahead on the first choice, we only enter it if the next two tokens are “foo” followed by “bar”, so if the next two tokens happen to be “foo” followed by something other than “bar” we enter the second choice.

Now, here is an interesting point: if the next two tokens are “foo” and “bat”, say, then the second choice is incorrect as well, but since we did not specify any lookahead there, we enter the second expansion based on it matching the first token. (Again: that's all we check by default.) Of course, we have a problem when we consume the “foo” and the next token is not what we want – a “bat” instead of a “bar”.

BUT… here is an important conceptual issue in terms of understanding the logic of a parser generator like this:

In terms of the topic of this page, choice points and lookaheads, this is simply not an issue!

(Huh?)

It's really quite simple, you see. The second token, “baz”, in the second expansion choice is not a choice point! So, yes, the generated parser at this point is expecting a “baz” but encountering a “bat”. So yes, it will complain! (Okay, when I say “complain”, that is an anthropomorphic way of saying that an exception will be thrown.)

But that is all right and proper. Look at it this way… the above grammar snippet says that we look ahead 2 tokens to decide whether to enter the first expansion. Since we didn't specify anything for the second expansion, it now just looks ahead one token, and if an error occurs after that, then so be it. The parser is doing what it is supposed to do, right? You see, in this case, none of the three given choices can match the “foo” “bat” sequence anyway! So, end result: it throws an exception, which is what it should do. But the point is that we can have “foo” “bar” here or “foo” “baz” and the generated parser will handle both cases. Our first go at this, without the lookahead specified, it would spuriously reject a “foo” “bar”, even though it is valid input! If it rejects “foo” “bat” that is not really an issue in terms of the problem we have set ourselves here because that is not valid input anyway!

Well, if you don't currently understand my point there, then all I can say is just think about it. It will eventually become clear…

More than one way to skin a cat...

The definite numerical lookahead of two tokens worked okay in the above example, but generally speaking, it is a rather crude disposition. The legacy JavaCC tool provides two other ways to specify how we resolve a choice – when the default resolution is not good enough. In the original, somewhat inaccurate terminology, they are called syntactic and semantic lookahead. JavaCC 21, meanwhile, provides two other tools, contextual_predicates and up to here.

Syntactic Lookahead

Syntactic lookahead allows you to specify a separate lookahead expansion which is checked as a condition at the choice point. We could have used this in the previous example, instead of writing SCAN 2, we could write:

    SCAN "foo" "bar" => "foo" bar" ....
    |
    "foo" "baz"
    |
    "bar" "bat"

This means that we scan ahead to see if the input matches the expansion “foo” “bar”, and if so, we enter the first choice. This is effectively the same as what we had before, since matching “foo” “bar” is the same as scanning ahead and matching the first two tokens. In the general case, however, syntactic lookahead is much more powerful because the lookahead expansion can be of variable length, and also it can match input of indefinite length. For example, consider:

   ( SCAN ( "foo" )+ "bar" => Foobar )?

The above is a zero or one choice and we scan ahead to check whether the coming input is one or more “foo”s followed by a “bar”. There is no precise amount of numerical lookahead that would handle this case if we really are going to accept any number of “foo”s followed by a bar! The above generates a scan routine that scans forward past all the “foo” tokens and then checks if the very next token is a “bar” and only then, enters the Foobar production. So, if the input is “foo” “bar”, that matches. If the input is “foo” “foo” “foo” “foo” “bar” that matches too. But if the input is “foo” “foo” … and a thousand more “foos” followed by a “baz”, then that is no good.

In short, the above syntactic lookahead generates code that scans as far forward as necessary. That might just be two tokens, but it could be 2000 tokens! (In practice that is probably rare, but at least in theory…)

(N.B. Sometimes, it is easier to express a condition by what it is not than by what it is. So, in JavaCC, you can put a tilde “~” in front of the lookahead expansion to indicate that you only match if it does not match. Note, however, that negative syntactic lookahead does not exist in legacy JavaCC.)

Semantic Lookahead

Semantic lookahead is actually a misnomer. It just means that the condition is expressed with arbitrary Java code. So, it might look ahead or it might do something else entirely. It's just Java code that is injected in this spot to resolve the choice at a choice point. It looks like:

    SCAN {someCondition()} => Foo Bar Baz

The code in the braces is just any Java expression that resolves to a boolean (true/false) value, a black box from the point of view of our parser generator.

Newer dispositions in JavaCC 21

Since the lookahead expansion when using syntactic lookahead so frequently is substantially (or exactly) the same as the expansion to be parsed, JavaCC 21 has the up to here syntax to express these conditions more tersely. For example, in legacy JavaCC, one frequently had constructs like:

   [ LOOKAHEAD (Foo() Bar()) Foo() Bar() Baz() ]

In JavaCC 21 this is expressed more tersely using the new streamlined syntax:

   [ SCAN Foo Bar => Foo Bar Baz ]

However, this is still annoyingly repetitious, so the up to here syntax was introduced for these cases and we can write:

    [ Foo Bar =>|| Baz ]

In very many common usage cases, the up to here syntax removes the need to write separate syntactic or numerical lookaheads.

By the same token, semantic lookahead, i.e. using arbitrary Java code to express conditions should always be a last resort. The new contextual_predicates feature in JavaCC 21 was designed to drastically reduce the cases in which it is necessary.)

A general rule of thumb would be to use up to here and contextual_predicates constructs whenever possible instead of syntactic and semantic lookahead respectively. (Though, granted, if it is not possible, then it's not possible.)

choice_points.txt · Last modified: 2021/02/08 18:09 by revusky