Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ cabal.sandbox.config
.*.swo
/.vscode/
tests/*.hs
*~
TAGS
8 changes: 8 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Revision history for Happy

## 2.2

* Error out when `<$>` appears in semantic action code
([#335](https://github.com/haskell/happy/issues/335)).

* Tested with GHC 8.0 - 9.14.1.
The Haskell code generated by Happy is for GHC 8.0 and up.

## 2.1.7

* Add support for `{-# OPTIONS_HAPPY ... #-}` pragmas
Expand Down
3 changes: 3 additions & 0 deletions doc/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ It is an error to refer to ``$i`` when ``i`` is larger than the number of symbol
The symbol ``$`` may be inserted literally in the Haskell expression using the sequence ``\$`` (this isn't necessary inside a string or character literal).

Additionally, the sequence ``$>`` can be used to represent the value of the rightmost symbol.
Since version 2.2, Happy throws an error when ``<$>`` appears in the Haskell expression;
Happy interprets this as ``< $>`` but users often mean the operator version of ``fmap``.
(Observe that the latter is available as ``<\$>``.)

A semantic value of the form ``{% ... }`` is a *monadic action*, and is only valid when the grammar file contains a ``%monad`` directive (:ref:`Monad Directive <sec-monad-decl>`).
Monadic actions are discussed in :ref:`Monadic Parsers <sec-monads>`.
Expand Down
7 changes: 5 additions & 2 deletions happy.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 1.18
name: happy
version: 2.1.7
version: 2.2
license: BSD2
license-file: LICENSE
copyright: (c) Andy Gill, Simon Marlow
Expand Down Expand Up @@ -114,6 +114,9 @@ extra-source-files:
tests/error001.stderr
tests/error001.stdout
tests/error001.y
tests/issue335.stderr
tests/issue335.stdout
tests/issue335.y
tests/monad001.y
tests/monad002.ly
tests/monaderror.y
Expand Down Expand Up @@ -141,7 +144,7 @@ executable happy
main-is: Main.lhs

build-depends: base >= 4.9 && < 5,
happy-lib == 2.1.7
happy-lib == 2.2

default-language: Haskell98
default-extensions: CPP, MagicHash, FlexibleContexts, NamedFieldPuns
Expand Down
48 changes: 31 additions & 17 deletions lib/frontend/src/Happy/Frontend/Mangler.lhs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ go do special processing. If not, pass on to the regular processing routine
> ns -> ns
> error_resumptive | ResumptiveErrorHandler{} <- getError dirs = True
> | otherwise = False
>

> start_strs = [ startName++'_':p | (TokenName p _ _) <- starts' ]

Build up a mapping from name values to strings.
Expand Down Expand Up @@ -132,7 +132,7 @@ Start symbols...
Deal with priorities...

> priodir = zip [1..] (getPrios dirs)
>

> mkPrio :: Int -> Directive a -> Priority
> mkPrio i (TokenNonassoc _) = Prio None i
> mkPrio i (TokenRight _) = Prio RightAssoc i
Expand All @@ -155,10 +155,10 @@ Translate the rules from string to name-based.
> convNT (Rule1 nt prods ty)
> = do nt' <- mapToName nt
> return (nt', prods, ty)
>

> transRule (nt, prods, _ty)
> = mapM (finishRule nt) prods
>

> finishRule :: Name -> Prod1 e -> Writer [ErrMsg] (Production e)
> finishRule nt (Prod1 lhs code line prec)
> = mapWriter (\(a,e) -> (a, map (addLine line) e)) $ do
Expand All @@ -168,7 +168,7 @@ Translate the rules from string to name-based.
> Left s -> do addErr ("Undeclared precedence token: " ++ s)
> return (Production nt lhs' code' No)
> Right p -> return (Production nt lhs' code' p)
>

> mkPrec :: [Name] -> Prec -> Either String Priority
> mkPrec lhs PrecNone =
> case filter (flip elem terminal_names) lhs of
Expand All @@ -180,9 +180,9 @@ Translate the rules from string to name-based.
> case lookup s prioByString of
> Nothing -> Left s
> Just p -> Right p
>

> mkPrec _ PrecShift = Right PrioLowest
>

> -- in

> rules1 <- mapM convNT rules
Expand All @@ -191,7 +191,7 @@ Translate the rules from string to name-based.
> let
> type_env = [(nt, t) | Rule1 nt _ (Just (t,[])) <- rules] ++
> [(nt, getTokenType dirs) | nt <- terminal_strs] -- XXX: Doesn't handle $$ type!
>

> fixType (ty,s) = go "" ty
> where go acc [] = return (reverse acc)
> go acc (c:r) | isLower c = -- look for a run of alphanumerics starting with a lower case letter
Expand All @@ -205,14 +205,14 @@ Translate the rules from string to name-based.
> go1 (c:cs)
> Just t -> go1 $ "(" ++ t ++ ")"
> | otherwise = go (c:acc) r
>

> convType (nm, t)
> = do t' <- fixType t
> return (nm, t')
>

> -- in
> tys <- mapM convType [ (nm, t) | (nm, _, Just t) <- rules1 ]
>


> let
> type_array :: Array Name (Maybe String)
Expand All @@ -238,7 +238,7 @@ Get the token specs in terms of Names.
> lookup_prods :: Name -> [Int]
> lookup_prods x | x >= firstStartTok && x < first_t = arr ! x
> lookup_prods _ = error "lookup_prods"
>

> productions' = start_prods ++ concat rules2
> prod_array = listArray (0,length productions' - 1) productions'

Expand Down Expand Up @@ -278,7 +278,7 @@ Gofer-like stuff:
> combine [] = []
> combine ((a,b):(c,d):r) | a == c = combine ((a,b++d) : r)
> combine (a:r) = a : combine r
>


For combining actions with possible error messages.

Expand Down Expand Up @@ -307,12 +307,15 @@ So is this.
-- At the same time, we collect a list of the variables actually used in this
-- code, which is used by the backend.

> doCheckCode :: Int -> String -> M (String, [Int])
> doCheckCode ::
> Int -- ^ Arity of the rule, i.e., maximal number of a variable.
> -> String -- ^ Haskell code for the semantic action of the rule.
> -> M (String, [Int]) -- ^ The translated code and the variable occurrences (in reverse order).
> doCheckCode arity code0 = go code0 "" []
> where go code acc used =
> case code of
> [] -> return (reverse acc, used)
>

> '"' :r -> case reads code :: [(String,String)] of
> [] -> go r ('"':acc) used
> (s,r'):_ -> go r' (reverse (show s) ++ acc) used
Expand All @@ -321,13 +324,24 @@ So is this.
> [] -> go r ('\'':acc) used
> (c,r'):_ -> go r' (reverse (show c) ++ acc) used
> '\\':'$':r -> go r ('$':acc) used
>


Issue #335 (Andreas Abel, 2026-01-14):
Users often write '<$>' to mean '`fmap`', but Happy interprets it as '< $>' where '$>'
denotes the maximal variable (rightmost item).
Thus, reject '<$>' in semantic action code and suggest to either escape the '$'
or put a space after '<'.

> c@'<':r@('$':'>':_) -> do
> addErr "Found '<$>' in semantic action code; either write '<\\$>' (for what usually is fmap) or '< $>' (for \"less than rightmost item\")"
> go r (c:acc) used

> '$':'>':r -- the "rightmost token"
> | arity == 0 -> do addErr "$> in empty rule"
> go r acc used
> | otherwise -> go r (reverse (mkHappyVar arity) ++ acc)
> (arity : used)
>

> '$':r@(i:_) | isDigit i ->
> case reads r :: [(Int,String)] of
> (j,r'):_ ->
Expand Down
2 changes: 1 addition & 1 deletion lib/happy-lib.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 3.0
name: happy-lib
version: 2.1.7
version: 2.2
license: BSD-2-Clause
copyright: (c) Andy Gill, Simon Marlow
author: Andy Gill and Simon Marlow
Expand Down
2 changes: 2 additions & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
*.n.hs
*.stderr
*.stdout
!issue335.stderr
!issue335.stdout
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
ERROR_TESTS = error001.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
2 changes: 2 additions & 0 deletions tests/issue335.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
issue335.y: 7: Found '<$>' in semantic action code; either write '<\$>' (for what usually is fmap) or '< $>' (for "less than rightmost item")

Empty file added tests/issue335.stdout
Empty file.
6 changes: 6 additions & 0 deletions tests/issue335.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%name foo
%tokentype { String }

%%

foo : foo { void <$> putStr "The '<$>' shall mean 'fmap' but happy will read it as '< $>' which is not what we want." }