Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ cabal.sandbox.config
.*.swo
/.vscode/
tests/*.hs
*~
3 changes: 3 additions & 0 deletions happy.cabal
Original file line number Diff line number Diff line change
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
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\")"
Comment thread
sgraf812 marked this conversation as resolved.
Resolved
> 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: 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." }