Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Revision history for Happy

## 2.2.2

* #353 broke some programs using `expect% <n>` declarations.
This release changes `%expect` semantics to interpret `n` as
an upper bound instead to allow for backward compatibility.
A non-fatal warning is emitted when `n` is not matched exactly.

## 2.2.1

* Omit some useless productions due to an off-by-one error and
Expand Down
12 changes: 8 additions & 4 deletions app/Main.lhs
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,19 @@ Pretty print the AbsSyn.
Report any conflicts in the grammar.

> case expect common_options of
> Just n | n == sr && rr == 0 -> return ()
> Just _ | rr > 0 ->
> die ("The grammar has reduce/reduce conflicts.\n" ++
> "This is not allowed when an expect directive is given\n")
> Just _ ->
> Just n | sr > n ->
> die ("The grammar has " ++ show sr ++
> " shift/reduce conflicts.\n" ++
> "This is different from the number given in the " ++
> "expect directive\n")
> "This is more than the " ++ show n ++
> " given in the %expect directive\n")
> Just n | sr < n ->
> do hPutStrLn stderr ("shift/reduce conflicts: " ++ show sr)
> hPutStrLn stderr ("(the %expect directive permits up to " ++
> show n ++ ")")
> Just _ -> return ()
> _ -> do

> (if sr /= 0
Expand Down
9 changes: 6 additions & 3 deletions doc/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,14 @@ These conflicts generate warnings.
But when you have checked the warnings and made sure that Happy handles them correctly these warnings are just annoying.
The ``%expect`` directive gives a way of avoiding them.
Declaring ``%expect n`` is a way of telling Happy
“There are exactly <n> shift/reduce conflicts and zero reduce/reduce conflicts in this grammar.
“There are at most <n> shift/reduce conflicts and zero reduce/reduce conflicts in this grammar.
I promise I have checked them and they are resolved correctly”.
When processing the grammar, Happy will check the actual number of conflicts against the ``%expect`` declaration if any, and if there is a discrepancy then an error will be reported.
When processing the grammar, Happy will check the actual number of conflicts against the ``%expect`` declaration if any, and if the number exceeds ``n`` then an error will be reported.
If the number if below ``n``, a non-fatal warnings will be emitted instead.

Happy's ``%expect`` directive works exactly like that of yacc.
Happy's ``%expect`` directive works like that of yacc, but relaxes the check to
“at most <n> conflicts” instead of “exactly <n> conflicts” to allow for
backward compatible improvements of the state machine.

.. _sec-error-directive:

Expand Down
2 changes: 1 addition & 1 deletion tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ TESTS = Test.ly TestMulti.ly TestPrecedence.ly bug001.ly \
AttrGrammar001.y AttrGrammar002.y \
Pragma.y

ERROR_TESTS = error001.y issue335.y
ERROR_TESTS = error001.y expect_upper.y issue335.y

# NOTE: `cabal` will set the `happy_datadir` env-var accordingly before invoking the test-suite
#TEST_HAPPY_OPTS = --strict --template=..
Expand Down
35 changes: 35 additions & 0 deletions tests/expect_upper.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- Test that %expect checks for an upper bound, not an exact match.
-- This grammar has exactly 1 shift/reduce conflict (the dangling else).
-- %expect 2 should succeed because 1 <= 2.

{
module Main where
}

%expect 2
%name parse
%tokentype { Token }

%token
'if' { If }
'then' { Then }
'else' { Else }
'x' { X }

%%

stmt : 'if' 'x' 'then' stmt 'else' stmt { $4 ++ $6 }
| 'if' 'x' 'then' stmt { $4 }
| 'x' { "x" }

{
main :: IO ()
main =
if parse [If, X, Then, X] == "x"
then return ()
else error "bad parse"

data Token = If | Then | Else | X

happyError _ = error "parse error"
}
Loading