Skip to content

Commit 83098a6

Browse files
authored
Implement block anaphora (#65)
* Extend anaphora logic to allow block anaphora * Implement block anaphora.
1 parent 3af0831 commit 83098a6

File tree

12 files changed

+332
-110
lines changed

12 files changed

+332
-110
lines changed

src/Eucalypt/Core/Anaphora.hs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE MultiParamTypeClasses #-}
12
{-|
23
Module : Eucalypt.Core.Syn
34
Description : Facilities for handling anaphoric params in Eucalypt syntax
@@ -13,21 +14,25 @@ module Eucalypt.Core.Anaphora where
1314
-- syntax, notably expression anaphora ('_', '_0', '_1'), and string
1415
-- anaphora ({}, {0}, {1}).
1516
--
16-
-- This typeclass allows us to treat them similarly
17-
class (Eq a, Show a) => Anaphora a where
17+
-- This typeclass allows us to treat them similarly.
18+
--
19+
-- 't' is the anaphor type (block, expression, string...)
20+
-- 'a' is the binding name type that expresses the anaphor (e.g.
21+
-- 'CoreBindingName' or 'Var')
22+
class (Eq a, Show a) => Anaphora t a where
1823

1924
-- | The unnumberedAnaphor for this type (e.g. '_') where the index
2025
-- is implicitly inferred from sequenc
21-
unnumberedAnaphor :: a
26+
unnumberedAnaphor :: t -> a
2227

2328
-- | Is a an anaphor (numbered or not)
24-
isAnaphor :: a -> Bool
29+
isAnaphor :: t -> a -> Bool
2530

2631
-- | Read the index from an anaphor if it is numbered
27-
toNumber :: a -> Maybe Int
32+
toNumber :: t -> a -> Maybe Int
2833

2934
-- | Create an anaphor for the specified number
30-
fromNumber :: Int -> a
35+
fromNumber :: t -> Int -> a
3136

3237
-- | A name for rendering
33-
toName :: a -> String
38+
toName :: t -> a -> String

src/Eucalypt/Core/BlockAnaphora.hs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
{-|
3+
Module : Eucalypt.Core.BlockAnaphora
4+
Description : Process block anaphora to turn anaphoric blocks to lambdas
5+
Copyright : (c) Greg Hawkins, 2018
6+
License :
7+
Maintainer : greg@curvelogic.co.uk
8+
Stability : experimental
9+
-}
10+
module Eucalypt.Core.BlockAnaphora
11+
( anaphorise
12+
, hasNakedBlockAnaphora
13+
) where
14+
15+
import Bound
16+
import Data.Bifunctor (second)
17+
import Eucalypt.Core.Anaphora
18+
import Eucalypt.Core.Syn
19+
20+
21+
-- | Process block expressions containing block anaphora into lambdas.
22+
--
23+
-- This processing assumes that all CoreLets represent blocks and so
24+
-- should be applied early in the pipeline - directly after desugaring
25+
-- so that it is not disrupted by any transformations or optimisations
26+
-- which disturb lets.
27+
anaphorise :: (Anaphora SymbolicAnaphora a) => CoreExp a -> CoreExp a
28+
anaphorise = transform False
29+
30+
31+
32+
-- | Search from this level to see if we have any block anaphora which
33+
-- are not contained within a block expression.
34+
hasNakedBlockAnaphora :: (Anaphora SymbolicAnaphora a) => CoreExp a -> Bool
35+
hasNakedBlockAnaphora (CoreVar _ n) = isAnaphor blockAnaphora n
36+
hasNakedBlockAnaphora CoreLet{} = False
37+
hasNakedBlockAnaphora (CoreLambda _ _ _ b) =
38+
(hasNakedBlockAnaphora . fromScope) b
39+
hasNakedBlockAnaphora (CoreMeta _ m e) =
40+
hasNakedBlockAnaphora m || hasNakedBlockAnaphora e
41+
hasNakedBlockAnaphora CoreBlock{} = False
42+
hasNakedBlockAnaphora (CoreList _ exprs) = any hasNakedBlockAnaphora exprs
43+
hasNakedBlockAnaphora (CoreArgTuple _ exprs) = any hasNakedBlockAnaphora exprs
44+
hasNakedBlockAnaphora (CoreOperator _ _ _ expr) = hasNakedBlockAnaphora expr
45+
hasNakedBlockAnaphora (CoreOpSoup _ exprs) = any hasNakedBlockAnaphora exprs
46+
hasNakedBlockAnaphora (CoreApply _ f xs) = any hasNakedBlockAnaphora (f : xs)
47+
hasNakedBlockAnaphora _ = False
48+
49+
50+
-- | Transform any anaphoric blocks into Lambdas
51+
transform :: (Anaphora SymbolicAnaphora a) => Bool -> CoreExp a -> CoreExp a
52+
transform True expr = expr
53+
transform False expr@(CoreLet smid bs b) =
54+
if any hasNakedBlockAnaphora $ map fromScope (b : map snd bs)
55+
then (bindAnaphora blockAnaphora . numberAnaphora blockAnaphora) expr
56+
else let b' = toScope $ transform False (fromScope b)
57+
bs' = map (second (toScope . transform False . fromScope)) bs
58+
in CoreLet smid bs' b'
59+
transform False (CoreLambda smid inl ns b) =
60+
let b' = toScope (transform False (fromScope b))
61+
in CoreLambda smid inl ns b'
62+
transform False expr@(CoreMeta smid m e) =
63+
let anaphoric = hasNakedBlockAnaphora expr
64+
m' = transform anaphoric m
65+
e' = transform anaphoric e
66+
in CoreMeta smid m' e'
67+
transform False (CoreBlock smid e) =
68+
let anaphoric = hasNakedBlockAnaphora e
69+
e' = transform anaphoric e
70+
in CoreBlock smid e'
71+
transform False (CoreList smid es) =
72+
let anaphoric = any hasNakedBlockAnaphora es
73+
es' = map (transform anaphoric) es
74+
in CoreList smid es'
75+
transform False (CoreArgTuple smid es) =
76+
let anaphoric = any hasNakedBlockAnaphora es
77+
es' = map (transform anaphoric) es
78+
in CoreArgTuple smid es'
79+
transform False (CoreOperator smid x p e) =
80+
let anaphoric = hasNakedBlockAnaphora e
81+
e' = transform anaphoric e
82+
in CoreOperator smid x p e'
83+
transform False (CoreLookup smid o n) =
84+
let anaphoric = hasNakedBlockAnaphora o
85+
o' = transform anaphoric o
86+
in CoreLookup smid o' n
87+
transform False expr@(CoreApply smid f xs) =
88+
let anaphoric = hasNakedBlockAnaphora expr
89+
f' = transform anaphoric f
90+
xs' = map (transform anaphoric) xs
91+
in CoreApply smid f' xs'
92+
transform False (CoreOpSoup smid xs) =
93+
let anaphoric = any hasNakedBlockAnaphora xs
94+
xs' = map (transform anaphoric) xs
95+
in CoreOpSoup smid xs'
96+
transform False expr = expr

src/Eucalypt/Core/Cook.hs

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE FlexibleContexts #-}
12
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23
{-# LANGUAGE DeriveFunctor #-}
34
{-# LANGUAGE LambdaCase #-}
@@ -64,31 +65,37 @@ distributeFixities e = e
6465

6566
-- | A core pass prior to evaluation to cook all soup that can be
6667
-- cooked.
67-
cookAllSoup :: Anaphora a => CoreExp a -> Interpreter (CoreExp a)
68+
cookAllSoup ::
69+
(Anaphora SymbolicAnaphora a) => CoreExp a -> Interpreter (CoreExp a)
6870
cookAllSoup = Interpreter . cookBottomUp False
6971

7072
-- | Entrypoint for evaluator if soup is discovered at runtime
71-
cook :: Anaphora a => [CoreExp a] -> Interpreter (CoreExp a)
73+
cook :: (Anaphora SymbolicAnaphora a) => [CoreExp a] -> Interpreter (CoreExp a)
7274
cook es = case cookSoup False es of
7375
Right expr -> return expr
7476
Left err -> throwEvalError err
7577

7678
-- | Take sequence of expression in operator soup and rearrange into
7779
-- non-soup expression using operator fixity.
78-
cookSoup :: Anaphora a => Bool -> [CoreExp a] -> Either CoreError (CoreExp a)
80+
cookSoup ::
81+
(Anaphora SymbolicAnaphora a)
82+
=> Bool
83+
-> [CoreExp a]
84+
-> Either CoreError (CoreExp a)
7985
cookSoup parentAnaphoric es = do
8086
subcooked <- cookSubsoups inAnaphoricLambda filled
8187
expr <- evalState shunt (initState subcooked inAnaphoricLambda)
8288
if wrap
83-
then return $ (bindAnaphora . numberAnaphora) expr
89+
then return $ processAnaphora expr
8490
else return expr
8591
where
8692
(filled, imAnaphoric) = precook es
8793
wrap = imAnaphoric && not parentAnaphoric
8894
inAnaphoricLambda = parentAnaphoric || imAnaphoric
95+
processAnaphora = bindAnaphora expressionAnaphora . numberAnaphora expressionAnaphora
8996

9097
cookScope ::
91-
(Anaphora a, Eq b, Show b)
98+
(Eq b, Show b, Anaphora SymbolicAnaphora a)
9299
=> Bool
93100
-> Scope b CoreExp a
94101
-> Interpreter (Scope b CoreExp a)
@@ -99,18 +106,25 @@ cookScope anaphoric scope =
99106
--
100107
-- Go through each filling first so and determine whether there are
101108
-- anaphora at this level
102-
precook :: Anaphora a => [CoreExp a] -> ([CoreExp a], Bool)
109+
precook :: (Anaphora SymbolicAnaphora a) => [CoreExp a] -> ([CoreExp a], Bool)
103110
precook = fillGaps
104111

105112

106113

107114
-- | Recurse down cooking any operator soups from the bottom upwards
108-
cookSubsoups :: Anaphora a => Bool -> [CoreExp a] -> Either CoreError [CoreExp a]
115+
cookSubsoups ::
116+
(Anaphora SymbolicAnaphora a)
117+
=> Bool
118+
-> [CoreExp a]
119+
-> Either CoreError [CoreExp a]
109120
cookSubsoups anaphoric = mapM (cookBottomUp anaphoric)
110121

111122

112123
cookBottomUp ::
113-
Anaphora a => Bool -> CoreExp a -> Either CoreError (CoreExp a)
124+
(Anaphora SymbolicAnaphora a)
125+
=> Bool
126+
-> CoreExp a
127+
-> Either CoreError (CoreExp a)
114128
cookBottomUp anaphoric (CoreOpSoup _ exprs) = cookSoup anaphoric exprs
115129
cookBottomUp anaphoric (CoreArgTuple smid exprs) =
116130
CoreArgTuple smid <$> traverse (cookBottomUp anaphoric) exprs
@@ -133,7 +147,9 @@ cookBottomUp _ e = Right e
133147

134148

135149
-- | Run the shunting algorithm until finished or errored
136-
shunt :: Anaphora a => State (ShuntState a) (Either CoreError (CoreExp a))
150+
shunt ::
151+
(Show a, Anaphora SymbolicAnaphora a)
152+
=> State (ShuntState a) (Either CoreError (CoreExp a))
137153
shunt = (shunt1 `untilM_` finished) >> result
138154
where
139155
finished = complete <$> get
@@ -157,7 +173,7 @@ data ShuntState a = ShuntState
157173
, shuntInsideAnaphoricLambda :: Bool
158174
} deriving (Show)
159175

160-
initState :: Anaphora a => [CoreExp a] -> Bool -> ShuntState a
176+
initState :: [CoreExp a] -> Bool -> ShuntState a
161177
initState es anaphoric =
162178
ShuntState
163179
{ shuntOutput = []
@@ -298,7 +314,10 @@ pushback e = state $ \s -> ((), s {shuntSource = e : shuntSource s})
298314
-- | Check the expression can be safely followed by what's coming up
299315
-- in next in the source and insert catenation operator or implicit
300316
-- parameter otherwise (giving us haskell style "sections" @(+)@ etc.)
301-
ensureValidSequence :: Anaphora a => Maybe (CoreExp a) -> State (ShuntState a) ()
317+
ensureValidSequence ::
318+
(Anaphora SymbolicAnaphora a)
319+
=> Maybe (CoreExp a)
320+
-> State (ShuntState a) ()
302321
ensureValidSequence lhs =
303322
peekSource >>= \rhs ->
304323
case validExprSeq lhs rhs of
@@ -307,7 +326,7 @@ ensureValidSequence lhs =
307326
Nothing -> return ()
308327

309328
-- | A step of the shunting yard algorithm
310-
shunt1 :: Anaphora a => State (ShuntState a) ()
329+
shunt1 :: (Anaphora SymbolicAnaphora a) => State (ShuntState a) ()
311330
shunt1 =
312331
popNext >>= \case
313332
Just expr@CoreOperator {} -> ensureValidSequence (Just expr) >> seatOp expr
@@ -333,32 +352,38 @@ bindSides Nothing = (OpLike, OpLike)
333352

334353
-- | Two exprs are valid together if one is OpLike and one is
335354
-- ValueLike on the sides that touch
336-
validExprSeq :: Anaphora a => Maybe (CoreExp a) -> Maybe (CoreExp a) -> Maybe (CoreExp a)
355+
validExprSeq ::
356+
(Anaphora SymbolicAnaphora a)
357+
=> Maybe (CoreExp a)
358+
-> Maybe (CoreExp a)
359+
-> Maybe (CoreExp a)
337360
validExprSeq l r = filler ((snd . bindSides) l) ((fst . bindSides) r)
338361

339362
-- | We can make an invalid sequence valid by inserting a catenation
340363
-- op or an anaphoric parameter
341-
filler :: Anaphora a => BindSide -> BindSide -> Maybe (CoreExp a)
364+
filler :: (Anaphora SymbolicAnaphora a) => BindSide -> BindSide -> Maybe (CoreExp a)
342365
filler ValueLike ValueLike = Just catOp
343-
filler OpLike OpLike = Just $ return unnumberedAnaphor
366+
filler OpLike OpLike = Just $ return $ unnumberedAnaphor expressionAnaphora
344367
filler _ _ = Nothing
345368

346369
-- | Make a given expression valid by inserting catenation and
347370
-- anaphora as required and track whether the sequence contains any
348371
-- anaphora.
349-
fillGaps :: Anaphora a => [CoreExp a] -> ([CoreExp a], Bool)
372+
fillGaps :: (Anaphora SymbolicAnaphora a) => [CoreExp a] -> ([CoreExp a], Bool)
350373
fillGaps exprs =
351374
let (es, anaphoric) = foldl' accum ([], False) sides
352-
in (reverse es, anaphoric)
375+
in (reverse es, anaphoric)
353376
where
354377
sides = map Just exprs ++ [Nothing]
355-
anaphoricMaybe = getAny . foldMap Any . fmap isAnaphoricVar
378+
anaphoricMaybe =
379+
getAny . foldMap Any . fmap (isAnaphoricVar expressionAnaphora)
356380
accum (l, b) e =
357-
let (l', b') = case validExprSeq (headMay l) e of
358-
Just v
359-
| isAnaphoricVar v -> (v : l, True)
360-
| otherwise -> (v : l, b || anaphoricMaybe e)
361-
Nothing -> (l, b || anaphoricMaybe e)
362-
in case e of
363-
Just x -> (x: l', b')
364-
Nothing -> (l', b')
381+
let (l', b') =
382+
case validExprSeq (headMay l) e of
383+
Just v
384+
| isAnaphoricVar expressionAnaphora v -> (v : l, True)
385+
| otherwise -> (v : l, b || anaphoricMaybe e)
386+
Nothing -> (l, b || anaphoricMaybe e)
387+
in case e of
388+
Just x -> (x : l', b')
389+
Nothing -> (l', b')

src/Eucalypt/Core/Desugar.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,7 @@ translateBlock loc blk = do
290290
translateStringPattern :: SourceSpan -> [StringChunk] -> Translate CoreExpr
291291
translateStringPattern loc cs = do
292292
smid <- recordSpan loc
293-
return $
294-
bindAnaphora (numberAnaphora $ conc smid) >>= \(Reference v) ->
295-
anon Syn.var v
293+
return $ processAnaphora (conc smid) >>= \(Reference v) -> anon Syn.var v
296294
where
297295
sub :: StringChunk -> CoreExp Target
298296
sub (Interpolation InterpolationRequest {refTarget = t}) =
@@ -301,7 +299,7 @@ translateStringPattern loc cs = do
301299
exprs = map sub cs
302300
conc smid =
303301
Syn.app smid (anon Syn.bif "JOIN") [CoreList smid exprs, anon Syn.str ""]
304-
302+
processAnaphora = bindAnaphora () . numberAnaphora ()
305303

306304

307305
-- | Descend through the AST, translating to CoreExpr and recording

0 commit comments

Comments
 (0)