Don't allow identifier duplicates

This commit is contained in:
Eugen Wissner 2024-08-05 22:56:35 +02:00
parent 9cb9ab536f
commit a1863147f8
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
4 changed files with 51 additions and 17 deletions

1
TODO
View File

@ -1,4 +1,3 @@
# Name analysis # Name analysis
- Ensure type, procedure, variable and parameter names are unique. - Ensure type, procedure, variable and parameter names are unique.
- Check symbols used in procedures are defined.

View File

@ -13,10 +13,12 @@ import qualified Language.Elna.SymbolTable as SymbolTable
import Language.Elna.Types (Type(..)) import Language.Elna.Types (Type(..))
import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad (foldM) import Control.Monad (foldM)
import qualified Data.List.NonEmpty as NonEmpty
data Error data Error
= UndefinedTypeError Identifier = UndefinedTypeError Identifier
| UnexpectedTypeInfoError Info | UnexpectedTypeInfoError Info
| IdentifierAlreadyDefinedError Identifier
deriving (Eq, Show) deriving (Eq, Show)
newtype NameAnalysis a = NameAnalysis newtype NameAnalysis a = NameAnalysis
@ -29,7 +31,7 @@ instance Functor NameAnalysis
instance Applicative NameAnalysis instance Applicative NameAnalysis
where where
pure x = NameAnalysis $ pure x pure = NameAnalysis . pure
(NameAnalysis f) <*> (NameAnalysis x) = NameAnalysis $ f <*> x (NameAnalysis f) <*> (NameAnalysis x) = NameAnalysis $ f <*> x
instance Monad NameAnalysis instance Monad NameAnalysis
@ -49,13 +51,27 @@ program (AST.Program declarations)
declaration :: SymbolTable -> AST.Declaration -> NameAnalysis SymbolTable declaration :: SymbolTable -> AST.Declaration -> NameAnalysis SymbolTable
declaration globalTable (AST.TypeDefinition identifier typeExpression) declaration globalTable (AST.TypeDefinition identifier typeExpression)
= flip (SymbolTable.enter identifier) globalTable . TypeInfo = withSymbolTable globalTable (dataType typeExpression)
<$> withSymbolTable globalTable (dataType typeExpression) >>= flip (enter identifier) globalTable . TypeInfo
declaration globalTable (AST.ProcedureDefinition identifier parameters variables _body) = do declaration globalTable (AST.ProcedureDefinition identifier parameters variables _body) = do
parametersInfo <- mapM parameter parameters parametersInfo <- mapM parameter parameters
variableInfo <- mapM variableDeclaration variables variableInfo <- mapM variableDeclaration variables
let localTable = SymbolTable.fromList $ parametersInfo <> variableInfo newTable <- either (identifierAlreadyDefinedError . NonEmpty.head) pure
pure $ SymbolTable.enter identifier (ProcedureInfo localTable mempty) globalTable $ SymbolTable.fromList
$ parametersInfo <> variableInfo
enter identifier (ProcedureInfo newTable mempty) globalTable
enter :: Identifier -> Info -> SymbolTable -> NameAnalysis SymbolTable
enter identifier info table
= maybe (identifierAlreadyDefinedError identifier) pure
$ SymbolTable.enter identifier info table
identifierAlreadyDefinedError :: Identifier -> NameAnalysis a
identifierAlreadyDefinedError = NameAnalysis
. lift
. throwE
. IdentifierAlreadyDefinedError
variableDeclaration :: AST.VariableDeclaration -> NameAnalysis (Identifier, Info) variableDeclaration :: AST.VariableDeclaration -> NameAnalysis (Identifier, Info)
variableDeclaration (AST.VariableDeclaration identifier typeExpression) variableDeclaration (AST.VariableDeclaration identifier typeExpression)

View File

@ -2,15 +2,19 @@ module Language.Elna.SymbolTable
( Info(..) ( Info(..)
, ParameterInfo(..) , ParameterInfo(..)
, SymbolTable , SymbolTable
, builtInSymbolTable
, empty , empty
, enter , enter
, fromList , fromList
, lookup , lookup
, builtInSymbolTable , member
) where ) where
import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap import qualified Data.HashMap.Strict as HashMap
import Data.List (sort)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Vector (Vector) import Data.Vector (Vector)
import Language.Elna.Location (Identifier(..)) import Language.Elna.Location (Identifier(..))
import Language.Elna.Types (Type(..), intType, booleanType) import Language.Elna.Types (Type(..), intType, booleanType)
@ -25,7 +29,7 @@ instance Semigroup SymbolTable
instance Monoid SymbolTable instance Monoid SymbolTable
where where
mempty = SymbolTable HashMap.empty mempty = empty
builtInSymbolTable :: SymbolTable builtInSymbolTable :: SymbolTable
builtInSymbolTable = SymbolTable $ HashMap.fromList builtInSymbolTable = SymbolTable $ HashMap.fromList
@ -36,15 +40,30 @@ builtInSymbolTable = SymbolTable $ HashMap.fromList
empty :: SymbolTable empty :: SymbolTable
empty = SymbolTable HashMap.empty empty = SymbolTable HashMap.empty
enter :: Identifier -> Info -> SymbolTable -> SymbolTable enter :: Identifier -> Info -> SymbolTable -> Maybe SymbolTable
enter identifier info (SymbolTable table) = SymbolTable enter identifier info table@(SymbolTable hashTable)
$ HashMap.insert identifier info table | member identifier table = Nothing
| otherwise = Just
$ SymbolTable
$ HashMap.insert identifier info hashTable
lookup :: Identifier -> SymbolTable -> Maybe Info lookup :: Identifier -> SymbolTable -> Maybe Info
lookup identifier (SymbolTable table) = HashMap.lookup identifier table lookup identifier (SymbolTable table) = HashMap.lookup identifier table
fromList :: [(Identifier, Info)] -> SymbolTable member :: Identifier -> SymbolTable -> Bool
fromList = SymbolTable . HashMap.fromList member identifier (SymbolTable table) = HashMap.member identifier table
fromList :: [(Identifier, Info)] -> Either (NonEmpty Identifier) SymbolTable
fromList elements
| Just identifierDuplicates' <- identifierDuplicates =
Left identifierDuplicates'
| otherwise = Right $ SymbolTable $ HashMap.fromList elements
where
identifierDuplicates = NonEmpty.nonEmpty
$ fmap NonEmpty.head
$ filter ((> 1) . NonEmpty.length)
$ NonEmpty.group . sort
$ fst <$> elements
data ParameterInfo = ParameterInfo data ParameterInfo = ParameterInfo
{ name :: Identifier { name :: Identifier

View File

@ -63,7 +63,7 @@ spec = describe "nameAnalysis" $ do
case SymbolTable.lookup "main" <$> actual of case SymbolTable.lookup "main" <$> actual of
Right lookupResult Right lookupResult
| Just (ProcedureInfo localTable _) <- lookupResult -> | Just (ProcedureInfo localTable _) <- lookupResult ->
localTable `shouldBe` expected Just localTable `shouldBe` expected
_ -> expectationFailure "Procedure symbol not found" _ -> expectationFailure "Procedure symbol not found"
it "puts variables into the local symbol table" $ do it "puts variables into the local symbol table" $ do
@ -74,5 +74,5 @@ spec = describe "nameAnalysis" $ do
case SymbolTable.lookup "main" <$> actual of case SymbolTable.lookup "main" <$> actual of
Right lookupResult Right lookupResult
| Just (ProcedureInfo localTable _) <- lookupResult -> | Just (ProcedureInfo localTable _) <- lookupResult ->
localTable `shouldBe` expected Just localTable `shouldBe` expected
_ -> expectationFailure "Procedure symbol not found" _ -> expectationFailure "Procedure symbol not found"