Skip to content

Commit 7887106

Browse files
committed
Implements Class/Derive/InstanceClause in Frontend
1 parent 6ace204 commit 7887106

File tree

12 files changed

+783
-200
lines changed

12 files changed

+783
-200
lines changed

lambda-buffers-frontend/lambda-buffers-frontend.cabal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,15 @@ library
9898
hs-source-dirs: src
9999
exposed-modules:
100100
LambdaBuffers.Frontend
101+
LambdaBuffers.Frontend.CheckReferences
102+
LambdaBuffers.Frontend.Errors
103+
LambdaBuffers.Frontend.Monad
101104
LambdaBuffers.Frontend.Parsec
102105
LambdaBuffers.Frontend.PPrint
106+
LambdaBuffers.Frontend.Scope
103107
LambdaBuffers.Frontend.Syntax
104108
LambdaBuffers.Frontend.ToProto
109+
LambdaBuffers.Frontend.Utils
105110

106111
executable lbf
107112
import: common-language
Lines changed: 35 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,24 @@
1-
module LambdaBuffers.Frontend (runFrontend, FrontendError (..), FrontendResult (..), Symbol, Scope, parseModule) where
1+
module LambdaBuffers.Frontend (runFrontend, FrontendError (..), FrontendResult (..), parseModule) where
22

3-
import Control.Monad (foldM, void, when)
4-
import Control.Monad.Except (ExceptT, runExceptT)
3+
import Control.Monad (foldM, when)
4+
import Control.Monad.Except (runExceptT)
55
import Control.Monad.State.Strict (MonadIO (liftIO), MonadTrans (lift), StateT (runStateT), gets, modify)
6-
import Control.Monad.Trans.Except (throwE)
76
import Control.Monad.Trans.Reader (ReaderT (runReaderT), asks, local)
8-
import Data.Foldable (for_)
97
import Data.List (isSuffixOf)
10-
import Data.Map (Map)
118
import Data.Map qualified as Map
12-
import Data.Maybe (fromMaybe)
13-
import Data.Set (Set)
14-
import Data.Set qualified as Set
159
import Data.Text (Text, unpack)
1610
import Data.Text.IO qualified as Text
17-
import Data.Traversable (for)
11+
import LambdaBuffers.Frontend.CheckReferences (checkReferences)
12+
import LambdaBuffers.Frontend.Errors (FrontendError)
13+
import LambdaBuffers.Frontend.Errors qualified as Errors
14+
import LambdaBuffers.Frontend.Monad (FrontRead (FrontRead, current, importPaths, visited), FrontState (FrontState, importedModules), FrontendResult (FrontendResult), FrontendT, throwE')
1815
import LambdaBuffers.Frontend.PPrint ()
1916
import LambdaBuffers.Frontend.Parsec qualified as Parsec
20-
import LambdaBuffers.Frontend.Syntax (Constructor (Constructor), Field (Field), Import (Import, importInfo, importModuleName), Module (moduleImports, moduleName, moduleTyDefs), ModuleAlias (ModuleAlias), ModuleName (ModuleName), ModuleNamePart (ModuleNamePart), Product (Product), Record (Record), SourceInfo, Sum (Sum), Ty (TyApp, TyRef', TyVar), TyBody (Opaque, ProductBody, RecordBody, SumBody), TyDef (TyDef, tyBody, tyDefInfo, tyName), TyName (TyName), TyRef (TyRef), defSourceInfo)
21-
import Prettyprinter (Doc, LayoutOptions (layoutPageWidth), PageWidth (Unbounded), Pretty (pretty), defaultLayoutOptions, layoutPretty, (<+>))
22-
import Prettyprinter.Render.String (renderShowS)
17+
import LambdaBuffers.Frontend.Scope (addToClassScope, addToTyScope, collectImportedScope, collectLocalScope)
18+
import LambdaBuffers.Frontend.Syntax (Import (importModuleName), Module (moduleImports, moduleName), ModuleName (ModuleName), ModuleNamePart (ModuleNamePart), SourceInfo, defSourceInfo)
19+
import LambdaBuffers.Frontend.Utils (ClassScope, TyScope, strip)
2320
import System.Directory (findFiles)
2421
import System.FilePath (joinPath, (<.>))
25-
import Text.Parsec (ParseError)
26-
27-
data FrontRead = FrontRead
28-
{ current :: ModuleName SourceInfo
29-
, visited :: [ModuleName ()]
30-
, importPaths :: [FilePath]
31-
}
32-
deriving stock (Eq, Show)
33-
34-
newtype FrontState = FrontState
35-
{ importedModules :: Map (ModuleName ()) (Module SourceInfo, Scope)
36-
}
37-
deriving stock (Eq, Show)
38-
39-
type Symbol = TyRef ()
40-
type Scope = Map Symbol (ModuleName SourceInfo)
41-
42-
data FrontendError
43-
= ModuleNotFound (ModuleName SourceInfo) (Import SourceInfo) [FilePath]
44-
| MultipleModulesFound (ModuleName SourceInfo) (Import SourceInfo) [FilePath]
45-
| ImportCycleFound (ModuleName SourceInfo) (Import SourceInfo) [ModuleName ()]
46-
| ModuleParseError FilePath ParseError
47-
| ImportedNotFound (ModuleName SourceInfo) (ModuleName SourceInfo) (TyName SourceInfo) (Set (TyName SourceInfo))
48-
| InvalidModuleFilepath (ModuleName SourceInfo) FilePath FilePath
49-
| SymbolAlreadyImported (ModuleName SourceInfo) (Import SourceInfo) Symbol (ModuleName SourceInfo)
50-
| TyRefNotFound (ModuleName SourceInfo) (TyRef SourceInfo) Scope
51-
| DuplicateTyDef (ModuleName SourceInfo) (TyDef SourceInfo)
52-
| TyDefNameConflict (ModuleName SourceInfo) (TyDef SourceInfo) (ModuleName SourceInfo)
53-
deriving stock (Eq)
54-
55-
newtype FrontendResult = FrontendResult
56-
{ processedModules :: Map (ModuleName ()) (Module SourceInfo, Scope)
57-
}
58-
deriving stock (Eq, Show)
59-
60-
showOneLine :: Doc a -> String
61-
showOneLine d = (renderShowS . layoutPretty (defaultLayoutOptions {layoutPageWidth = Unbounded}) $ d) ""
62-
63-
instance Show FrontendError where
64-
show (ModuleNotFound _cm imp impPaths) = showOneLine $ pretty (importInfo imp) <+> "Module" <+> pretty (importModuleName imp) <+> "not found in available import paths" <+> pretty impPaths
65-
show (MultipleModulesFound _cm imp conflictingPaths) = showOneLine $ pretty (importInfo imp) <+> "Module" <+> pretty (importModuleName imp) <+> "found in multiple files" <+> pretty conflictingPaths
66-
show (ImportCycleFound _cm imp visited) = showOneLine $ pretty (importInfo imp) <+> "Tried to load module" <+> pretty (importModuleName imp) <+> "which constitutes a cycle" <+> pretty visited
67-
show (ModuleParseError _fp err) = showOneLine $ pretty err
68-
show (ImportedNotFound _cm mn tn@(TyName _ info) available) = showOneLine $ pretty info <+> "Type" <+> pretty tn <+> "not found in module" <+> pretty mn <> ", did you mean one of" <+> pretty (Set.toList available)
69-
show (InvalidModuleFilepath mn@(ModuleName _ info) gotModFp wantedFpSuffix) = showOneLine $ pretty info <+> "File name" <+> pretty gotModFp <+> "doesn't match module name" <+> pretty mn <+> "expected" <+> pretty wantedFpSuffix
70-
show (SymbolAlreadyImported _cm imp sym alreadyInModuleName) = showOneLine $ pretty (importInfo imp) <+> "Symbol" <+> pretty sym <+> "already imported from module" <+> pretty alreadyInModuleName
71-
show (TyRefNotFound _cm tyR@(TyRef _ _ info) scope) = showOneLine $ pretty info <+> "Type " <> pretty tyR <+> "not found in the module's scope" <+> (pretty . Map.keys $ scope)
72-
show (DuplicateTyDef _cm tyDef) = showOneLine $ pretty (tyDefInfo tyDef) <+> "Duplicate type definition with the name" <+> pretty (tyName tyDef)
73-
show (TyDefNameConflict _cm tyDef imn) = showOneLine $ pretty (tyDefInfo tyDef) <+> "Type name" <+> pretty (tyName tyDef) <+> "conflicts with an imported type name from module" <+> pretty imn
74-
75-
type FrontendT m a = MonadIO m => ReaderT FrontRead (StateT FrontState (ExceptT FrontendError m)) a
7622

7723
-- | Run a Frontend compilation action on a "lbf" file, return the entire compilation closure or a frontend error.
7824
runFrontend :: MonadIO m => [FilePath] -> FilePath -> m (Either FrontendError FrontendResult)
@@ -82,29 +28,23 @@ runFrontend importPaths modFp = do
8228
ioM = runExceptT exM
8329
fmap (FrontendResult . importedModules . snd) <$> ioM
8430

85-
throwE' :: FrontendError -> FrontendT m a
86-
throwE' = lift . lift . throwE
87-
8831
moduleNameToFilepath :: ModuleName info -> FilePath
8932
moduleNameToFilepath (ModuleName parts _) = joinPath [unpack p | ModuleNamePart p _ <- parts] <.> "lbf"
9033

9134
checkCycle :: Import SourceInfo -> FrontendT m ()
9235
checkCycle imp = do
9336
ms <- asks visited
9437
cm <- asks current
95-
when ((strip . importModuleName $ imp) `elem` ms) $ throwE' $ ImportCycleFound cm imp ms
38+
when ((strip . importModuleName $ imp) `elem` ms) $ throwE' $ Errors.ImportCycleFound cm imp ms
9639

97-
-- | Parse a LambdaBuffers modules with a specified filename (for reporting) and content.
40+
-- | `parseModule fp txt` parses a LambdaBuffers module with a specified filename `fp` (for reporting) and content in `txt`.
9841
parseModule :: FilePath -> Text -> FrontendT m (Module SourceInfo)
9942
parseModule modFp modContent = do
10043
modOrErr <- liftIO $ Parsec.runParser Parsec.parseModule modFp modContent
10144
case modOrErr of
102-
Left err -> throwE' $ ModuleParseError modFp err
45+
Left err -> throwE' $ Errors.ModuleParseError modFp err
10346
Right m -> return m
10447

105-
strip :: Functor f => f a -> f ()
106-
strip = void
107-
10848
importModule :: Import SourceInfo -> FrontendT m (Module SourceInfo)
10949
importModule imp = do
11050
let modName = importModuleName imp
@@ -116,16 +56,16 @@ importModule imp = do
11656
case found of
11757
[] -> do
11858
cm <- asks current
119-
throwE' $ ModuleNotFound cm imp ips
59+
throwE' $ Errors.ModuleNotFound cm imp ips
12060
[modFp] -> do
12161
modContent <- liftIO $ Text.readFile modFp
12262
modOrErr <- liftIO $ Parsec.runParser Parsec.parseModule modFp modContent
12363
case modOrErr of
124-
Left err -> throwE' $ ModuleParseError modFp err
64+
Left err -> throwE' $ Errors.ModuleParseError modFp err
12565
Right m -> return m
12666
modFps -> do
12767
cm <- asks current
128-
throwE' $ MultipleModulesFound cm imp modFps
68+
throwE' $ Errors.MultipleModulesFound cm imp modFps
12969
Just (m, _) -> return m
13070

13171
processFile :: FilePath -> FrontendT m (Module SourceInfo)
@@ -147,101 +87,34 @@ processModule m = local
14787
importedScope <- processImports m
14888
localScope <- collectLocalScope m importedScope
14989
let modScope = localScope <> importedScope
150-
checkReferences modScope m
90+
checkReferences m modScope
15191
_ <- lift $ modify (FrontState . Map.insert (strip . moduleName $ m) (m, modScope) . importedModules)
15292
return m
15393

154-
checkReferences :: Scope -> Module SourceInfo -> FrontendT m ()
155-
checkReferences scope m = for_ (moduleTyDefs m) (checkBody . tyBody)
156-
where
157-
checkBody (SumBody (Sum cs _)) = for_ cs checkConstructor
158-
checkBody (ProductBody (Product fields _)) = for_ fields checkTy
159-
checkBody (RecordBody (Record fields _)) = for_ fields (\(Field _ ty _) -> checkTy ty)
160-
checkBody Opaque = return ()
161-
162-
checkConstructor (Constructor _ (Product tys _) _) = for tys checkTy
163-
164-
checkTy (TyApp tyF tyAs _) = checkTy tyF >> for_ tyAs checkTy
165-
checkTy (TyVar _) = return ()
166-
checkTy (TyRef' tyR _) =
167-
if Map.member (strip tyR) scope
168-
then return ()
169-
else do
170-
cm <- asks current
171-
throwE' $ TyRefNotFound cm tyR scope
172-
17394
checkModuleName :: FilePath -> ModuleName SourceInfo -> FrontendT m ()
17495
checkModuleName fp mn =
17596
let suffix = moduleNameToFilepath mn
17697
in if suffix `isSuffixOf` fp
17798
then return ()
178-
else throwE' $ InvalidModuleFilepath mn fp suffix
179-
180-
processImports :: Module SourceInfo -> FrontendT m Scope
181-
processImports m =
182-
foldM
183-
( \totalScope imp -> do
184-
scope <- processImport imp
185-
foldM
186-
( \totalScope' sym -> case Map.lookup sym totalScope' of
187-
Nothing -> return $ Map.insert sym (importModuleName imp) totalScope'
188-
Just mn -> do
189-
cm <- asks current
190-
throwE' $ SymbolAlreadyImported cm imp sym mn
191-
)
192-
totalScope
193-
scope
194-
)
195-
mempty
196-
(moduleImports m)
197-
198-
processImport :: Import SourceInfo -> FrontendT m (Set Symbol)
99+
else throwE' $ Errors.InvalidModuleFilepath mn fp suffix
100+
101+
processImports :: Module SourceInfo -> FrontendT m (TyScope, ClassScope)
102+
processImports m = do
103+
(totalTyScope, totalClassScope) <-
104+
foldM
105+
( \(totalTyScope', totalClassScope') imp ->
106+
do
107+
(tyScope, classScope) <- processImport imp
108+
(,)
109+
<$> addToTyScope totalTyScope' (imp, tyScope)
110+
<*> addToClassScope totalClassScope' (imp, classScope)
111+
)
112+
mempty
113+
(moduleImports m)
114+
return (totalTyScope, totalClassScope)
115+
116+
processImport :: Import SourceInfo -> FrontendT m (TyScope, ClassScope)
199117
processImport imp = do
200118
checkCycle imp
201119
im <- importModule imp >>= processModule
202120
collectImportedScope imp im
203-
204-
collectImportedScope :: Import SourceInfo -> Module SourceInfo -> FrontendT m (Set Symbol)
205-
collectImportedScope (Import isQual modName mayImports mayAlias _) m =
206-
let availableTyNs = [tyN | (TyDef tyN _ _ _) <- moduleTyDefs m]
207-
availableTyNs' = Set.fromList availableTyNs
208-
importedTyNs = fromMaybe availableTyNs mayImports
209-
availableSTyNs = Set.fromList $ strip <$> availableTyNs
210-
in foldM
211-
( \total tyN ->
212-
let styN = strip tyN
213-
in if Set.member styN availableSTyNs
214-
then return . Set.union total . Set.fromList $
215-
case mayAlias of
216-
Nothing ->
217-
if isQual
218-
then [TyRef (Just $ ModuleAlias (strip modName) ()) styN ()]
219-
else [TyRef (Just $ ModuleAlias (strip modName) ()) styN (), TyRef Nothing styN ()]
220-
Just al ->
221-
if isQual
222-
then [TyRef (Just . strip $ al) styN ()]
223-
else [TyRef (Just . strip $ al) styN (), TyRef Nothing styN ()]
224-
else do
225-
cm <- asks current
226-
throwE' $ ImportedNotFound cm modName tyN availableTyNs'
227-
)
228-
Set.empty
229-
importedTyNs
230-
231-
collectLocalScope :: Module SourceInfo -> Scope -> FrontendT m Scope
232-
collectLocalScope m importedScope =
233-
foldM
234-
( \totalScope tyDef@(TyDef tn _ _ _) -> do
235-
let tyR = TyRef Nothing (strip tn) ()
236-
case Map.lookup tyR totalScope of
237-
Nothing -> case Map.lookup tyR importedScope of
238-
Nothing -> return $ Map.insert tyR (moduleName m) totalScope
239-
Just im -> do
240-
cm <- asks current
241-
throwE' $ TyDefNameConflict cm tyDef im
242-
Just _ -> do
243-
cm <- asks current
244-
throwE' $ DuplicateTyDef cm tyDef
245-
)
246-
mempty
247-
(moduleTyDefs m)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module LambdaBuffers.Frontend.CheckReferences (checkReferences) where
2+
3+
import Control.Monad.Trans.Reader (asks)
4+
import Data.Foldable (for_)
5+
import Data.Map qualified as Map
6+
import LambdaBuffers.Frontend.Errors qualified as Errors
7+
import LambdaBuffers.Frontend.Monad (FrontRead (current), FrontendT, throwE')
8+
import LambdaBuffers.Frontend.PPrint ()
9+
import LambdaBuffers.Frontend.Syntax (ClassConstraint (ClassConstraint), ClassDef (classSupers), ClassRef (ClassRef), Constraint (Constraint), Constructor (Constructor), Derive (Derive), Field (Field), InstanceClause (instanceBody, instanceHead), Module (moduleStatements), Product (Product), Record (Record), SourceInfo, Statement (StClassDef, StDerive, StInstanceClause, StTyDef), Sum (Sum), Ty (TyApp, TyRef', TyVar), TyBody (Opaque, ProductBody, RecordBody, SumBody), TyDef (tyBody), TyRef (TyRef))
10+
import LambdaBuffers.Frontend.Utils (ClassScope, TyScope, strip)
11+
12+
checkReferences :: Module SourceInfo -> (TyScope, ClassScope) -> FrontendT m ()
13+
checkReferences m scope@(tyScope, classScope) = do
14+
for_
15+
(moduleStatements m)
16+
( \case
17+
StTyDef tds -> checkTyDef tyScope tds
18+
StClassDef cds -> checkClassDef classScope cds
19+
StInstanceClause ics -> checkInstanceClause scope ics
20+
StDerive drv -> checkDerive scope drv
21+
)
22+
23+
checkTyDef :: TyScope -> TyDef SourceInfo -> FrontendT m ()
24+
checkTyDef tyScope = checkBody . tyBody
25+
where
26+
checkBody :: TyBody SourceInfo -> FrontendT m ()
27+
checkBody (SumBody (Sum cs _)) = for_ cs checkConstructor
28+
checkBody (ProductBody (Product fields _)) = for_ fields (checkTy tyScope)
29+
checkBody (RecordBody (Record fields _)) = for_ fields (\(Field _ ty _) -> checkTy tyScope ty)
30+
checkBody Opaque = return ()
31+
32+
checkConstructor :: Constructor SourceInfo -> FrontendT m ()
33+
checkConstructor (Constructor _ (Product tys _) _) = for_ tys (checkTy tyScope)
34+
35+
checkClassDef :: ClassScope -> ClassDef SourceInfo -> FrontendT m ()
36+
checkClassDef classScope = checkSups . classSupers
37+
where
38+
checkSups :: [ClassConstraint SourceInfo] -> FrontendT m ()
39+
checkSups sups = for_ sups checkClassConstraint
40+
41+
checkClassConstraint :: ClassConstraint SourceInfo -> FrontendT m ()
42+
checkClassConstraint (ClassConstraint cr _) = checkClassRef classScope cr
43+
44+
checkClassRef :: ClassScope -> ClassRef SourceInfo -> FrontendT m ()
45+
checkClassRef classScope cr@(ClassRef mayAlias clN _) =
46+
if Map.member (strip <$> mayAlias, strip clN) classScope
47+
then return ()
48+
else do
49+
cm <- asks current
50+
throwE' $ Errors.classRefNotFoundErr cm cr classScope
51+
52+
checkInstanceClause :: (TyScope, ClassScope) -> InstanceClause SourceInfo -> FrontendT m ()
53+
checkInstanceClause scope inst = do
54+
checkConstraint scope (instanceHead inst)
55+
for_ (instanceBody inst) (checkConstraint scope)
56+
57+
checkDerive :: (TyScope, ClassScope) -> Derive SourceInfo -> FrontendT m ()
58+
checkDerive scope (Derive cstr) = checkConstraint scope cstr
59+
60+
checkConstraint :: (TyScope, ClassScope) -> Constraint SourceInfo -> FrontendT m ()
61+
checkConstraint (tyScope, classScope) (Constraint cr tys _) = do
62+
checkClassRef classScope cr
63+
for_ tys (checkTy tyScope)
64+
65+
checkTy :: TyScope -> Ty SourceInfo -> FrontendT m ()
66+
checkTy tyScope (TyApp tyF tyAs _) = checkTy tyScope tyF >> for_ tyAs (checkTy tyScope)
67+
checkTy _tyScope (TyVar _) = return ()
68+
checkTy tyScope (TyRef' tr@(TyRef mayAlias tyN _) _) =
69+
if Map.member (strip <$> mayAlias, strip tyN) tyScope
70+
then return ()
71+
else do
72+
cm <- asks current
73+
throwE' $ Errors.tyRefNotFoundErr cm tr tyScope

0 commit comments

Comments
 (0)