From a4cffc1eeb547f780068875a703251db6aa41d6c Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Thu, 26 Sep 2024 00:28:41 -0600 Subject: Rename some of the stages. Stage1 -> Parsed Stage2 -> Expanded Stage3 -> Checked --- src/Language/Fiddle/Ast/Internal/Stage.hs | 63 +-- src/Language/Fiddle/Ast/Internal/SyntaxTree.hs | 565 +++++++++++++++-------- src/Language/Fiddle/Compiler/ConsistencyCheck.hs | 523 +++++++++++++++++++++ src/Language/Fiddle/Compiler/Expansion.hs | 230 +++++++++ src/Language/Fiddle/Compiler/Stage0.hs | 8 +- src/Language/Fiddle/Compiler/Stage1.hs | 230 --------- src/Language/Fiddle/Compiler/Stage2.hs | 514 --------------------- src/Language/Fiddle/Parser.hs | 12 +- src/Main.hs | 6 +- 9 files changed, 1169 insertions(+), 982 deletions(-) create mode 100644 src/Language/Fiddle/Compiler/ConsistencyCheck.hs create mode 100644 src/Language/Fiddle/Compiler/Expansion.hs delete mode 100644 src/Language/Fiddle/Compiler/Stage1.hs delete mode 100644 src/Language/Fiddle/Compiler/Stage2.hs (limited to 'src') diff --git a/src/Language/Fiddle/Ast/Internal/Stage.hs b/src/Language/Fiddle/Ast/Internal/Stage.hs index cfcd0e6..20460b6 100644 --- a/src/Language/Fiddle/Ast/Internal/Stage.hs +++ b/src/Language/Fiddle/Ast/Internal/Stage.hs @@ -3,6 +3,7 @@ {-# LANGUAGE IncoherentInstances #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} + module Language.Fiddle.Ast.Internal.Stage where import Data.Type.Bool @@ -11,33 +12,43 @@ import Data.Typeable import qualified GHC.TypeError as TypeError import GHC.TypeLits --- Stage of compilation. Parts of the AST maybe un unavailable with other stages --- as compilation simplifies the AST. -data Stage = Stage1 | Stage2 | Stage3 +-- | Represents the different stages of the compilation process. +-- Different stages may have distinct versions of the Abstract Syntax Tree (AST), +-- as the compilation process simplifies or transforms the tree. +data Stage + = Parsed + | Expanded + | Checked deriving (Typeable) --- Returns the stage before the given stage. -type family PreviousStage (s :: Stage) :: Stage where - PreviousStage Stage1 = TypeError (TypeError.Text "No Prior stage to Stage1") - PreviousStage s = NumberToStage (StageToNumber s - 1) +-- | Converts a 'Stage' into a type-level natural number. This mapping allows +-- us to use numeric comparisons at the type level to reason about stages. +-- For example, 'Parsed' maps to 1, 'Expanded' to 2, and so on. +type family StageToNumber (s :: Stage) :: Natural where + StageToNumber Parsed = 1 + StageToNumber Expanded = 2 + StageToNumber Checked = 3 --- Returns the stage after the give stage. -type family NextStage (s :: Stage) :: Stage where - NextStage s = NumberToStage (StageToNumber s + 1) +-- | A type-level constraint that checks if one compilation stage precedes another. +-- It compares the numeric values associated with each stage using 'CmpNat'. +-- If 'stage1' is less than 'stage2', the result will be 'True'. +-- This is useful to conditionally include or exclude parts of the AST +-- depending on the compilation stage. +type StagePreceeds stage1 stage2 = + (CmpNat (StageToNumber stage1) (StageToNumber stage2) == LT) --- Converts a stage to a number. -type family StageToNumber (s :: Stage) :: Natural where - StageToNumber Stage1 = 1 - StageToNumber Stage2 = 2 - StageToNumber Stage3 = 3 - --- Converts a number to a stage. -type family NumberToStage (n :: Natural) :: Stage where - NumberToStage 1 = Stage1 - NumberToStage 2 = Stage2 - NumberToStage 3 = Stage3 - NumberToStage n = TypeError (TypeError.Text "Number in NumberToStage.") - --- Type-level constraint to determine if a stage is less than some natural --- ordinal. Used to bound parts of the AST in multiple stages. -type StageLessThan stage (n :: Natural) = (CmpNat (StageToNumber stage) n == LT) +type (<) a b = StagePreceeds a b + +-- | A type-level constraint that checks if one compilation stage succeeds another. +-- Similar to 'StagePreceeds', it compares the numeric values of stages. +-- Returns 'True' if 'stage1' comes after 'stage2'. +type StageSucceeds stage1 stage2 = + (CmpNat (StageToNumber stage1) (StageToNumber stage2) == LT) + +type (>) a b = StagePreceeds a b + +-- | A type-level function that compares two stages and returns a comparison +-- result ('LT', 'EQ', or 'GT'). This function is a generalized way to compare +-- the order of stages numerically. +type CmpStage stage1 stage2 = + CmpNat (StageToNumber stage1) (StageToNumber stage2) diff --git a/src/Language/Fiddle/Ast/Internal/SyntaxTree.hs b/src/Language/Fiddle/Ast/Internal/SyntaxTree.hs index 827f712..d03a855 100644 --- a/src/Language/Fiddle/Ast/Internal/SyntaxTree.hs +++ b/src/Language/Fiddle/Ast/Internal/SyntaxTree.hs @@ -73,18 +73,16 @@ import Language.Fiddle.Ast.Internal.Stage -- numbers are just strings like anything else. In later stages, numbers get -- parsed into actual integers. This makes it easier to process later. type family NumberType (a :: Stage) :: Type where - NumberType Stage1 = Text - NumberType Stage2 = Integer - NumberType s = NumberType (PreviousStage s) + NumberType s = If (s < Expanded) Text Integer -- The type that represents an import statement. In the early stages of -- compilation, this is just a string representing the import path, but in later -- stages of compilation, this actually gets replaced by an abstract -- representation of the imported material. type family ImportType (stage :: Stage) :: SynTree where - ImportType Stage1 = ImportStatement - ImportType Stage2 = ImportStatement - ImportType Stage3 = ImportStatement + ImportType Parsed = ImportStatement + ImportType Expanded = ImportStatement + ImportType Checked = ImportStatement -- A way to disable or enable a subtree type based on a type-level boolean. -- @@ -109,66 +107,91 @@ data Name :: SynTree where Name :: NonEmpty (Identifier f a) -> a -> Name f a deriving (Generic, Annotated, Alter, Typeable) --- | Represents a directive in the Fiddle language. A directive provides --- additional metadata or instructions that the compiler can use during --- code generation. Directives can be attached to many elements in the +-- | Represents a directive in the Fiddle language. A directive provides +-- additional metadata or instructions that the compiler can use during +-- code generation. Directives can be attached to many elements in the -- syntax tree. data Directive :: SynTree where Directive :: - { directiveBody :: f (DirectiveBody f a), -- ^ The body of the directive. - directiveAnnot :: a -- ^ Annotation for the directive. - } -> Directive f a + { -- | The body of the directive. + directiveBody :: f (DirectiveBody f a), + -- | Annotation for the directive. + directiveAnnot :: a + } -> + Directive f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents the body of a directive, which consists of multiple elements. data DirectiveBody :: SynTree where DirectiveBody :: - { directiveElements :: [DirectiveElement f a], -- ^ Elements of the directive. - directiveBodyAnnot :: a -- ^ Annotation for the directive body. - } -> DirectiveBody f a + { -- | Elements of the directive. + directiveElements :: [DirectiveElement f a], + -- | Annotation for the directive body. + directiveBodyAnnot :: a + } -> + DirectiveBody f a deriving (Generic, Annotated, Alter, Typeable) --- | Represents an element in a directive. Can be either a key or a key-value +-- | Represents an element in a directive. Can be either a key or a key-value -- pair. data DirectiveElement :: SynTree where -- | A simple directive element with a key. The mere presence of this key -- holds semantic value. DirectiveElementKey :: - { directiveBackend :: Maybe (Identifier f a), -- ^ Optional backend target. - directiveKey :: Identifier f a, -- ^ The key of the directive. - directiveKeyAnnot :: a -- ^ Annotation for the directive element. - } -> DirectiveElement f a + { -- | Optional backend target. + directiveBackend :: Maybe (Identifier f a), + -- | The key of the directive. + directiveKey :: Identifier f a, + -- | Annotation for the directive element. + directiveKeyAnnot :: a + } -> + DirectiveElement f a -- | A more complex directive element with a key-value pair, optionally -- specifying a backend. DirectiveElementKeyValue :: - { directiveBackend :: Maybe (Identifier f a), -- ^ Optional backend target. - directiveKey :: Identifier f a, -- ^ The key of the directive. - directiveValue :: DirectiveExpression f a, -- ^ The value of the directive. - directiveKeyValueAnnot :: a -- ^ Annotation for the key-value directive. - } -> DirectiveElement f a + { -- | Optional backend target. + directiveBackend :: Maybe (Identifier f a), + -- | The key of the directive. + directiveKey :: Identifier f a, + -- | The value of the directive. + directiveValue :: DirectiveExpression f a, + -- | Annotation for the key-value directive. + directiveKeyValueAnnot :: a + } -> + DirectiveElement f a deriving (Generic, Annotated, Alter, Typeable) --- | Represents expressions that can be used within a directive, either a +-- | Represents expressions that can be used within a directive, either a -- string or a number. data DirectiveExpression f a where - DirectiveString :: - { directiveStringValue :: Text, -- ^ String value of the directive. - directiveStringAnnot :: a -- ^ Annotation for the directive string. - } -> DirectiveExpression f a - DirectiveNumber :: - { directiveNumberValue :: Text, -- ^ Number value of the directive. - directiveNumberAnnot :: a -- ^ Annotation for the directive number. - } -> DirectiveExpression f a + DirectiveString :: + { -- | String value of the directive. + directiveStringValue :: Text, + -- | Annotation for the directive string. + directiveStringAnnot :: a + } -> + DirectiveExpression f a + DirectiveNumber :: + { -- | Number value of the directive. + directiveNumberValue :: Text, + -- | Annotation for the directive number. + directiveNumberAnnot :: a + } -> + DirectiveExpression f a deriving (Generic, Annotated, Alter, Typeable) --- | A type that wraps another syntax tree and applies a list of directives to +-- | A type that wraps another syntax tree and applies a list of directives to -- it. data Directed t stage f a where Directed :: - { directedDirectives :: [Directive f a], -- ^ List of directives. - directedSubtree :: t stage f a, -- ^ The wrapped syntax tree. - directedAnnot :: a -- ^ Annotation for the directed subtree. - } -> Directed t stage f a + { -- | List of directives. + directedDirectives :: [Directive f a], + -- | The wrapped syntax tree. + directedSubtree :: t stage f a, + -- | Annotation for the directed subtree. + directedAnnot :: a + } -> + Directed t stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Apply a function to the underlying subtree in a 'Directed' type. @@ -177,119 +200,168 @@ mapDirected fn (Directed dr tfa a) = Directed dr (fn tfa) a -- | Apply a monadic function to the underlying subtree in a 'Directed' type. mapDirectedM :: - (Monad m) => - (t s f a -> m (t' s' f a)) -> - Directed t s f a -> + (Monad m) => + (t s f a -> m (t' s' f a)) -> + Directed t s f a -> m (Directed t' s' f a) mapDirectedM fn (Directed dr tfa a) = Directed dr <$> fn tfa <*> pure a --- | Convert an annotated syntax tree element to a 'Directed' type with +-- | Convert an annotated syntax tree element to a 'Directed' type with -- an empty directive list. asDirected :: (Annotated (t s)) => t s f a -> Directed t s f a asDirected tfa = Directed [] tfa (annot tfa) --- | Extract the underlying subtree from a 'Directed' type, discarding any +-- | Extract the underlying subtree from a 'Directed' type, discarding any -- directives. undirected :: Directed t s f a -> t s f a undirected (Directed _ tfa _) = tfa -- | The root of the parse tree, containing a list of top-level declarations. data FiddleUnit (stage :: Stage) (f :: Type -> Type) a where - FiddleUnit :: - { fiddleDecls :: [Directed FiddleDecl stage f a], -- ^ List of declarations. - fiddleUnitAnnot :: a -- ^ Annotation for the 'FiddleUnit'. - } -> FiddleUnit stage f a + FiddleUnit :: + { -- | List of declarations. + fiddleDecls :: [Directed FiddleDecl stage f a], + -- | Annotation for the 'FiddleUnit'. + fiddleUnitAnnot :: a + } -> + FiddleUnit stage f a deriving (Generic, Annotated, Typeable) deriving instance (Alter (ImportType stage)) => Alter (FiddleUnit stage) -- | Represents an identifier with an associated annotation. -data Identifier f a = Identifier - { identifierName :: !Text, -- ^ The name of the identifier. - identifierAnnot :: a -- ^ Annotation for the identifier. +data Identifier f a = Identifier + { -- | The name of the identifier. + identifierName :: !Text, + -- | Annotation for the identifier. + identifierAnnot :: a } deriving (Generic, Annotated, Alter, Typeable) -- | Expressions used within Fiddle, including literals and variables. data Expression (s :: Stage) :: SynTree where -- | A numeric literal, whose value is dependent on the compilation stage. - LitNum :: - { litNumValue :: NumberType stage, -- ^ The numeric value. - litNumAnnot :: a -- ^ Annotation for the literal. - } -> Expression stage f a + LitNum :: + { -- | The numeric value. + litNumValue :: NumberType stage, + -- | Annotation for the literal. + litNumAnnot :: a + } -> + Expression stage f a -- | A variable reference. - Var :: - { varIdentifier :: Identifier f a, -- ^ The identifier of the variable. - varAnnot :: a -- ^ Annotation for the variable. - } -> Expression stage f a + Var :: + { -- | The identifier of the variable. + varIdentifier :: Identifier f a, + -- | Annotation for the variable. + varAnnot :: a + } -> + Expression stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents an import statement in the Fiddle language. data ImportStatement f a where - ImportStatement :: - { importPath :: Text, -- ^ The path to import. - importList :: Maybe (ImportList f a), -- ^ Optional list of imported items. - importStatementAnnot :: a -- ^ Annotation for the import statement. - } -> ImportStatement f a + ImportStatement :: + { -- | The path to import. + importPath :: Text, + -- | Optional list of imported items. + importList :: Maybe (ImportList f a), + -- | Annotation for the import statement. + importStatementAnnot :: a + } -> + ImportStatement f a deriving (Generic, Annotated, Alter, Typeable) -- | A list of imported identifiers. data ImportList f a where - ImportList :: - { importIdentifiers :: [Identifier f a], -- ^ The list of identifiers. - importListAnnot :: a -- ^ Annotation for the import list. - } -> ImportList f a + ImportList :: + { -- | The list of identifiers. + importIdentifiers :: [Identifier f a], + -- | Annotation for the import list. + importListAnnot :: a + } -> + ImportList f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents top-level declarations in Fiddle. data FiddleDecl :: StagedSynTree where -- | An option declaration in the form 'option '. OptionDecl :: - { optionKey :: Identifier f a, -- ^ The key of the option. - optionValue :: Identifier f a, -- ^ The value of the option. - optionAnnot :: a -- ^ Annotation for the option declaration. - } -> FiddleDecl stage f a + { -- | The key of the option. + optionKey :: Identifier f a, + -- | The value of the option. + optionValue :: Identifier f a, + -- | Annotation for the option declaration. + optionAnnot :: a + } -> + FiddleDecl stage f a -- | An import declaration. ImportDecl :: - { importType :: ImportType stage f a, -- ^ The imported type. - importDeclAnnot :: a -- ^ Annotation for the import declaration. - } -> FiddleDecl stage f a + { -- | The imported type. + importType :: ImportType stage f a, + -- | Annotation for the import declaration. + importDeclAnnot :: a + } -> + FiddleDecl stage f a -- | A using declaration. UsingDecl :: - { usingName :: Name f a, -- ^ The name being used. - usingAnnot :: a -- ^ Annotation for the using declaration. - } -> FiddleDecl stage f a + { -- | The name being used. + usingName :: Name f a, + -- | Annotation for the using declaration. + usingAnnot :: a + } -> + FiddleDecl stage f a -- | A package declaration. PackageDecl :: - { packageName :: Name f a, -- ^ The package name. - packageBody :: f (PackageBody stage f a), -- ^ The body of the package. - packageAnnot :: a -- ^ Annotation for the package declaration. - } -> FiddleDecl stage f a + { -- | The package name. + packageName :: Name f a, + -- | The body of the package. + packageBody :: f (PackageBody stage f a), + -- | Annotation for the package declaration. + packageAnnot :: a + } -> + FiddleDecl stage f a -- | A location declaration in the form 'location = '. LocationDecl :: - { locationIdent :: Identifier f a, -- ^ The location identifier. - locationExpr :: Expression stage f a, -- ^ The associated expression. - locationAnnot :: a -- ^ Annotation for the location declaration. - } -> FiddleDecl stage f a + { -- | The location identifier. + locationIdent :: Identifier f a, + -- | The associated expression. + locationExpr :: Expression stage f a, + -- | Annotation for the location declaration. + locationAnnot :: a + } -> + FiddleDecl stage f a -- | A bits declaration in the form 'bits : '. BitsDecl :: - { bitsIdent :: Identifier f a, -- ^ The identifier of the bits. - bitsType :: BitType stage f a, -- ^ The type of the bits. - bitsAnnot :: a -- ^ Annotation for the bits declaration. - } -> FiddleDecl stage f a + { -- | The identifier of the bits. + bitsIdent :: Identifier f a, + -- | The type of the bits. + bitsType :: BitType stage f a, + -- | Annotation for the bits declaration. + bitsAnnot :: a + } -> + FiddleDecl stage f a -- | An object type declaration. ObjTypeDecl :: - { objTypeIdent :: Identifier f a, -- ^ The identifier of the object type. - objTypeBody :: f (ObjTypeBody stage f a), -- ^ The body of the object type. - objTypeAnnot :: a -- ^ Annotation for the object type declaration. - } -> FiddleDecl stage f a + { -- | The identifier of the object type. + objTypeIdent :: Identifier f a, + -- | The body of the object type. + objTypeBody :: f (ObjTypeBody stage f a), + -- | Annotation for the object type declaration. + objTypeAnnot :: a + } -> + FiddleDecl stage f a -- | An object declaration in the form 'object at : '. ObjectDecl :: - { objectIdent :: Identifier f a, -- ^ The identifier of the object. - objectLocation :: Expression stage f a, -- ^ The location expression. - objectType :: ObjType stage f a, -- ^ The type of the object. - objectAnnot :: a -- ^ Annotation for the object declaration. - } -> FiddleDecl stage f a + { -- | The identifier of the object. + objectIdent :: Identifier f a, + -- | The location expression. + objectLocation :: Expression stage f a, + -- | The type of the object. + objectType :: ObjType stage f a, + -- | Annotation for the object declaration. + objectAnnot :: a + } -> + FiddleDecl stage f a deriving (Generic, Annotated, Typeable) deriving instance (Alter (ImportType stage)) => Alter (FiddleDecl stage) @@ -298,32 +370,47 @@ deriving instance (Alter (ImportType stage)) => Alter (FiddleDecl stage) -- union), a list of object declarations, and an annotation. data ObjTypeBody (stage :: Stage) (f :: Type -> Type) a where ObjTypeBody :: - { objBodyType :: BodyType f a, -- ^ The body type (struct or union). - objBodyDecls :: [Directed ObjTypeDecl stage f a], -- ^ Object declarations. - objBodyAnnot :: a -- ^ Annotation for the object type body. - } -> ObjTypeBody stage f a + { -- | The body type (struct or union). + objBodyType :: BodyType f a, + -- | Object declarations. + objBodyDecls :: [Directed ObjTypeDecl stage f a], + -- | Annotation for the object type body. + objBodyAnnot :: a + } -> + ObjTypeBody stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents an object type, which can be anonymous, an array, or a -- reference to another type. data ObjType stage f a where - -- | An anonymous object type, allowed only in Stage1. + -- | An anonymous object type, allowed only in Parsed. AnonymousObjType :: - { anonWitness :: Witness (stage == Stage1), -- ^ Witness for stage constraint. - anonBody :: f (ObjTypeBody stage f a), -- ^ The body of the anonymous type. - anonAnnot :: a -- ^ Annotation for the anonymous type. - } -> ObjType stage f a + { -- | Witness for stage constraint. + anonWitness :: Witness (stage == Parsed), + -- | The body of the anonymous type. + anonBody :: f (ObjTypeBody stage f a), + -- | Annotation for the anonymous type. + anonAnnot :: a + } -> + ObjType stage f a -- | An array of object types. ArrayObjType :: - { arrayObjType :: ObjType stage f a, -- ^ The type of the array elements. - arraySize :: Expression stage f a, -- ^ The size of the array. - arrayAnnot :: a -- ^ Annotation for the array type. - } -> ObjType stage f a + { -- | The type of the array elements. + arrayObjType :: ObjType stage f a, + -- | The size of the array. + arraySize :: Expression stage f a, + -- | Annotation for the array type. + arrayAnnot :: a + } -> + ObjType stage f a -- | A reference to an existing type by name. ReferencedObjType :: - { refName :: Name f a, -- ^ The name of the referenced type. - refAnnot :: a -- ^ Annotation for the referenced type. - } -> ObjType stage f a + { -- | The name of the referenced type. + refName :: Name f a, + -- | Annotation for the referenced type. + refAnnot :: a + } -> + ObjType stage f a deriving (Typeable, Generic, Alter, Annotated, Typeable) -- | Represents a declaration inside an object type, such as a register, an @@ -331,37 +418,57 @@ data ObjType stage f a where data ObjTypeDecl stage f a where -- | An assertion statement for a specific position. AssertPosStatement :: - { assertWitness :: Witness (StageLessThan stage 3), -- ^ Witness for stage constraint. - assertExpr :: Expression stage f a, -- ^ The expression for the assertion. - assertAnnot :: a -- ^ Annotation for the assertion. - } -> ObjTypeDecl stage f a + { -- | Witness for stage constraint. + assertWitness :: Witness (stage < Checked), + -- | The expression for the assertion. + assertExpr :: Expression stage f a, + -- | Annotation for the assertion. + assertAnnot :: a + } -> + ObjTypeDecl stage f a -- | A register declaration. RegisterDecl :: - { regModifier :: Maybe (Modifier f a), -- ^ Optional register modifier. - regIdent :: Maybe (Identifier f a), -- ^ Optional register identifier. - regSize :: Expression stage f a, -- ^ Size of the register. - regBody :: Maybe (RegisterBody stage f a), -- ^ Optional register body. - regAnnot :: a -- ^ Annotation for the register declaration. - } -> ObjTypeDecl stage f a + { -- | Optional register modifier. + regModifier :: Maybe (Modifier f a), + -- | Optional register identifier. + regIdent :: Maybe (Identifier f a), + -- | Size of the register. + regSize :: Expression stage f a, + -- | Optional register body. + regBody :: Maybe (RegisterBody stage f a), + -- | Annotation for the register declaration. + regAnnot :: a + } -> + ObjTypeDecl stage f a -- | A reserved declaration for padding or alignment. ReservedDecl :: - { reservedExpr :: Expression stage f a, -- ^ The expression for reserved space. - reservedAnnot :: a -- ^ Annotation for the reserved declaration. - } -> ObjTypeDecl stage f a + { -- | The expression for reserved space. + reservedExpr :: Expression stage f a, + -- | Annotation for the reserved declaration. + reservedAnnot :: a + } -> + ObjTypeDecl stage f a -- | A declaration for a substructure (struct or union). TypeSubStructure :: - { subStructureBody :: f (ObjTypeBody stage f a), -- ^ The body of the substructure. - subStructureName :: Maybe (Identifier f a), -- ^ Optional name for the substructure. - subStructureAnnot :: a -- ^ Annotation for the substructure. - } -> ObjTypeDecl stage f a + { -- | The body of the substructure. + subStructureBody :: f (ObjTypeBody stage f a), + -- | Optional name for the substructure. + subStructureName :: Maybe (Identifier f a), + -- | Annotation for the substructure. + subStructureAnnot :: a + } -> + ObjTypeDecl stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents a modifier for registers (e.g., read-only, read-write). data Modifier f a where ModifierKeyword :: - { modifierKey :: ModifierKeyword, -- ^ The keyword for the modifier. - modifierAnnot :: a -- ^ Annotation for the modifier. - } -> Modifier f a + { -- | The keyword for the modifier. + modifierKey :: ModifierKeyword, + -- | Annotation for the modifier. + modifierAnnot :: a + } -> + Modifier f a deriving (Generic, Annotated, Alter, Typeable) -- | Enumerates the different types of register modifiers. @@ -372,28 +479,39 @@ data ModifierKeyword = Rw | Ro | Wo -- declarations. data DeferredRegisterBody stage f a where DeferredRegisterBody :: - { deferredBits :: [Directed RegisterBitsDecl stage f a], -- ^ Bit declarations. - deferredAnnot :: a -- ^ Annotation for the deferred register body. - } -> DeferredRegisterBody stage f a + { -- | Bit declarations. + deferredBits :: [Directed RegisterBitsDecl stage f a], + -- | Annotation for the deferred register body. + deferredAnnot :: a + } -> + DeferredRegisterBody stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents the body type (struct or union) in an object. data BodyType (f :: Type -> Type) a where Union :: - { unionAnnot :: a -- ^ Annotation for the union. - } -> BodyType f a + { -- | Annotation for the union. + unionAnnot :: a + } -> + BodyType f a Struct :: - { structAnnot :: a -- ^ Annotation for the struct. - } -> BodyType f a + { -- | Annotation for the struct. + structAnnot :: a + } -> + BodyType f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents a register body with a body type and deferred bit declarations. data RegisterBody stage f a where RegisterBody :: - { regBodyType :: BodyType f a, -- ^ The body type of the register. - regDeferredBody :: f (DeferredRegisterBody stage f a), -- ^ Deferred body. - regBodyAnnot :: a -- ^ Annotation for the register body. - } -> RegisterBody stage f a + { -- | The body type of the register. + regBodyType :: BodyType f a, + -- | Deferred body. + regDeferredBody :: f (DeferredRegisterBody stage f a), + -- | Annotation for the register body. + regBodyAnnot :: a + } -> + RegisterBody stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents declarations within a register, such as defined bits, @@ -401,22 +519,34 @@ data RegisterBody stage f a where data RegisterBitsDecl stage f a where -- | Declaration for reserved bits. ReservedBits :: - { reservedBitsExpr :: Expression stage f a, -- ^ Expression for reserved bits. - reservedBitsAnnot :: a -- ^ Annotation for the reserved bits. - } -> RegisterBitsDecl stage f a + { -- | Expression for reserved bits. + reservedBitsExpr :: Expression stage f a, + -- | Annotation for the reserved bits. + reservedBitsAnnot :: a + } -> + RegisterBitsDecl stage f a -- | Declaration for defined bits in a register. DefinedBits :: - { definedBitsModifier :: Maybe (Modifier f a), -- ^ Optional modifier for the bits. - definedBitsIdent :: Identifier f a, -- ^ Identifier for the bits. - definedBitsTypeRef :: RegisterBitsTypeRef stage f a, -- ^ Type reference for the bits. - definedBitsAnnot :: a -- ^ Annotation for the defined bits. - } -> RegisterBitsDecl stage f a + { -- | Optional modifier for the bits. + definedBitsModifier :: Maybe (Modifier f a), + -- | Identifier for the bits. + definedBitsIdent :: Identifier f a, + -- | Type reference for the bits. + definedBitsTypeRef :: RegisterBitsTypeRef stage f a, + -- | Annotation for the defined bits. + definedBitsAnnot :: a + } -> + RegisterBitsDecl stage f a -- | Substructure within a register. BitsSubStructure :: - { bitsSubRegBody :: RegisterBody stage f a, -- ^ The body of the substructure. - bitsSubName :: Maybe (Identifier f a), -- ^ Optional name for the substructure. - bitsSubAnnot :: a -- ^ Annotation for the substructure. - } -> RegisterBitsDecl stage f a + { -- | The body of the substructure. + bitsSubRegBody :: RegisterBody stage f a, + -- | Optional name for the substructure. + bitsSubName :: Maybe (Identifier f a), + -- | Annotation for the substructure. + bitsSubAnnot :: a + } -> + RegisterBitsDecl stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents different ways to refer to register bits, either as an array, @@ -424,85 +554,122 @@ data RegisterBitsDecl stage f a where data RegisterBitsTypeRef stage f a where -- | An array of bits with a specified size. RegisterBitsArray :: - { bitsArrayTypeRef :: RegisterBitsTypeRef stage f a, -- ^ Reference to the array type. - bitsArraySize :: Expression stage f a, -- ^ Size of the array. - bitsArrayAnnot :: a -- ^ Annotation for the array. - } -> RegisterBitsTypeRef stage f a + { -- | Reference to the array type. + bitsArrayTypeRef :: RegisterBitsTypeRef stage f a, + -- | Size of the array. + bitsArraySize :: Expression stage f a, + -- | Annotation for the array. + bitsArrayAnnot :: a + } -> + RegisterBitsTypeRef stage f a -- | A reference to another type by name. RegisterBitsReference :: - { bitsRefName :: Name f a, -- ^ The name of the referenced type. - bitsRefAnnot :: a -- ^ Annotation for the reference. - } -> RegisterBitsTypeRef stage f a - -- | An anonymous type for register bits, used in Stage1. + { -- | The name of the referenced type. + bitsRefName :: Name f a, + -- | Annotation for the reference. + bitsRefAnnot :: a + } -> + RegisterBitsTypeRef stage f a + -- | An anonymous type for register bits, used in Parsed. RegisterBitsAnonymousType :: - { anonBitsWitness :: Witness (stage == Stage1), -- ^ Witness for stage constraint. - anonBitsType :: AnonymousBitsType stage f a, -- ^ The anonymous type. - anonBitsAnnot :: a -- ^ Annotation for the anonymous type. - } -> RegisterBitsTypeRef stage f a + { -- | Witness for stage constraint. + anonBitsWitness :: Witness (stage == Parsed), + -- | The anonymous type. + anonBitsType :: AnonymousBitsType stage f a, + -- | Annotation for the anonymous type. + anonBitsAnnot :: a + } -> + RegisterBitsTypeRef stage f a -- | A direct specification of bits as an expression. RegisterBitsJustBits :: - { justBitsExpr :: Expression stage f a, -- ^ Expression for the bits. - justBitsAnnot :: a -- ^ Annotation for the bits. - } -> RegisterBitsTypeRef stage f a + { -- | Expression for the bits. + justBitsExpr :: Expression stage f a, + -- | Annotation for the bits. + justBitsAnnot :: a + } -> + RegisterBitsTypeRef stage f a deriving (Generic, Annotated, Alter, Typeable) --- | Represents an anonymous bit type, such as an enum, used in Stage1. +-- | Represents an anonymous bit type, such as an enum, used in Parsed. data AnonymousBitsType stage f a where AnonymousEnumBody :: - { anonEnumExpr :: Expression stage f a, -- ^ Expression defining the enum size. - anonEnumBody :: f (EnumBody stage f a), -- ^ The body of the enum. - anonEnumAnnot :: a -- ^ Annotation for the anonymous enum. - } -> AnonymousBitsType stage f a + { -- | Expression defining the enum size. + anonEnumExpr :: Expression stage f a, + -- | The body of the enum. + anonEnumBody :: f (EnumBody stage f a), + -- | Annotation for the anonymous enum. + anonEnumAnnot :: a + } -> + AnonymousBitsType stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents a bit type, either an enumeration or raw bits. data BitType (stage :: Stage) (f :: Type -> Type) a where -- | An enumeration type for bits. EnumBitType :: - { enumBitExpr :: Expression stage f a, -- ^ Expression defining the enum size. - enumBitBody :: f (EnumBody stage f a), -- ^ The body of the enum. - enumBitAnnot :: a -- ^ Annotation for the enumeration. - } -> BitType stage f a + { -- | Expression defining the enum size. + enumBitExpr :: Expression stage f a, + -- | The body of the enum. + enumBitBody :: f (EnumBody stage f a), + -- | Annotation for the enumeration. + enumBitAnnot :: a + } -> + BitType stage f a -- | A raw bit type. RawBits :: - { rawBitsExpr :: Expression stage f a, -- ^ Expression defining the bits. - rawBitsAnnot :: a -- ^ Annotation for the raw bits. - } -> BitType stage f a + { -- | Expression defining the bits. + rawBitsExpr :: Expression stage f a, + -- | Annotation for the raw bits. + rawBitsAnnot :: a + } -> + BitType stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents the body of an enumeration. data EnumBody (stage :: Stage) (f :: Type -> Type) a where EnumBody :: - { enumConsts :: [Directed EnumConstantDecl stage f a], -- ^ Enum constant declarations. - enumBodyAnnot :: a -- ^ Annotation for the enum body. - } -> EnumBody stage f a + { -- | Enum constant declarations. + enumConsts :: [Directed EnumConstantDecl stage f a], + -- | Annotation for the enum body. + enumBodyAnnot :: a + } -> + EnumBody stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents a declaration for an enumeration constant. data EnumConstantDecl stage f a where -- | A named constant in the enum. EnumConstantDecl :: - { enumConstIdent :: Identifier f a, -- ^ Identifier for the constant. - enumConstExpr :: Expression stage f a, -- ^ Expression defining the constant. - enumConstAnnot :: a -- ^ Annotation for the constant. - } -> EnumConstantDecl stage f a + { -- | Identifier for the constant. + enumConstIdent :: Identifier f a, + -- | Expression defining the constant. + enumConstExpr :: Expression stage f a, + -- | Annotation for the constant. + enumConstAnnot :: a + } -> + EnumConstantDecl stage f a -- | A reserved value in the enum. EnumConstantReserved :: - { enumReservedExpr :: Expression stage f a, -- ^ Expression for the reserved value. - enumReservedAnnot :: a -- ^ Annotation for the reserved value. - } -> EnumConstantDecl stage f a + { -- | Expression for the reserved value. + enumReservedExpr :: Expression stage f a, + -- | Annotation for the reserved value. + enumReservedAnnot :: a + } -> + EnumConstantDecl stage f a deriving (Generic, Annotated, Alter, Typeable) -- | Represents the body of a package, containing a list of declarations. data PackageBody (stage :: Stage) (f :: Type -> Type) a where PackageBody :: - { packageBodyDecls :: [Directed FiddleDecl stage f a], -- ^ Declarations in the package. - packageBodyAnnot :: a -- ^ Annotation for the package body. - } -> PackageBody stage f a + { -- | Declarations in the package. + packageBodyDecls :: [Directed FiddleDecl stage f a], + -- | Annotation for the package body. + packageBodyAnnot :: a + } -> + PackageBody stage f a deriving (Generic, Annotated, Typeable) deriving instance (Alter (ImportType stage)) => Alter (PackageBody stage) - squeeze :: (Alter t, Traversable f, Monad f) => t f a -> f (t Identity a) squeeze = alter (fmap Identity) return diff --git a/src/Language/Fiddle/Compiler/ConsistencyCheck.hs b/src/Language/Fiddle/Compiler/ConsistencyCheck.hs new file mode 100644 index 0000000..90f4aa4 --- /dev/null +++ b/src/Language/Fiddle/Compiler/ConsistencyCheck.hs @@ -0,0 +1,523 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE IncoherentInstances #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} + +module Language.Fiddle.Compiler.ConsistencyCheck (checkConsistency) where + +import Control.Monad (forM, forM_, unless, when) +import Control.Monad.Identity (Identity (Identity)) +import Control.Monad.RWS (MonadState (get, put), MonadWriter (tell), gets, modify') +import Data.Foldable (Foldable (toList), foldlM) +import Data.Functor.Identity +import qualified Data.IntMap as IntMap +import Data.Kind (Type) +import Data.List (inits, intercalate) +import Data.List.NonEmpty (NonEmpty (..)) +import qualified Data.List.NonEmpty as NonEmpty +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import qualified Data.Set as Set +import qualified Data.Text as Text +import Data.Word (Word32) +import Language.Fiddle.Ast +import Language.Fiddle.Compiler +import Language.Fiddle.Internal.Scopes +import Language.Fiddle.Types (Commented (unCommented), SourceSpan) +import Text.Printf (printf) +import Prelude hiding (unzip) + +newtype GlobalState = GlobalState + { globalScope :: Scope String (Either SizeBits SizeBytes) + } + +newtype LocalState = LocalState (ScopePath String) + +type I = Identity + +type Annot = Commented SourceSpan + +type SizeBits = Word32 + +type SizeBytes = Word32 + +checkConsistency :: + FiddleUnit Expanded I Annot -> + Compile () (FiddleUnit Checked I Annot) +checkConsistency = + fmap snd + . subCompile (GlobalState mempty) + . advanceStage (LocalState mempty) + +instance CompilationStage Expanded where + type StageAfter Expanded = Checked + type StageMonad Expanded = Compile GlobalState + type StageState Expanded = LocalState + type StageFunctor Expanded = Identity + type StageAnnotation Expanded = Commented SourceSpan + +deriving instance AdvanceStage Expanded FiddleUnit + +deriving instance AdvanceStage Expanded Expression + +deriving instance AdvanceStage Expanded ObjType + +deriving instance AdvanceStage Expanded DeferredRegisterBody + +deriving instance AdvanceStage Expanded RegisterBitsDecl + +deriving instance AdvanceStage Expanded RegisterBitsTypeRef + +deriving instance AdvanceStage Expanded AnonymousBitsType + +deriving instance AdvanceStage Expanded BitType + +deriving instance AdvanceStage Expanded EnumBody + +deriving instance AdvanceStage Expanded EnumConstantDecl + +deriving instance AdvanceStage Expanded PackageBody + +deriving instance (AdvanceStage Expanded t) => AdvanceStage Expanded (Directed t) + +instance AdvanceStage Expanded RegisterBody where + advanceStage s body = fst <$> registerBodyToStage3 s body + +instance AdvanceStage Expanded ObjTypeBody where + advanceStage s body = fst <$> objTypeBodyToStage3 s body 0 + +instance AdvanceStage Expanded FiddleDecl where + modifyState t s = case t of + (BitsDecl id typ a) -> do + typeSize <- getTypeSize typ + insertTypeSize s id typeSize + return s + (PackageDecl n _ _) -> do + let strs = nameToList n + let (LocalState scopePath) = s + + return $ + LocalState $ + scopePath {currentScope = strs ++ currentScope scopePath} + (UsingDecl n _) -> + let (LocalState scopePath) = s + in return $ + LocalState $ + scopePath + { usingPaths = nameToList n : usingPaths scopePath + } + _ -> return s + +nameToList :: Name f a -> [String] +nameToList (Name idents _) = map (\(Identifier (Text.unpack -> s) _) -> s) (toList idents) + +objTypeBodyToStage3 :: + LocalState -> + ObjTypeBody Expanded I Annot -> + Word32 -> + Compile GlobalState (ObjTypeBody Checked I Annot, Word32) +objTypeBodyToStage3 st (ObjTypeBody bodyType decls a) startOff = do + let isUnion = case bodyType of + Union {} -> True + _ -> False + (cur, returned) <- + foldlM + ( \(cursor, returned) decl -> + case undirected decl of + RegisterDecl mMod mIdent expr mBody a -> do + (s3RegisterBody, mCalculatedSize) <- + fUnzip <$> mapM (registerBodyToStage3 st) mBody + + nExpr <- advanceStage st expr + + let s3 = + mapDirected + ( const $ + RegisterDecl + mMod + mIdent + nExpr + s3RegisterBody + a + ) + decl + + declaredSizeBits <- fromIntegral <$> exprToSize expr + + when ((declaredSizeBits `mod` 8) /= 0) $ + tell + [ Diagnostic + Error + "Register size is not a multiple of 8. Please pad register size to align with 8. " + (unCommented a) + ] + + forM_ mCalculatedSize $ \(fromIntegral -> calculatedSize) -> + unless (calculatedSize == declaredSizeBits) $ + let helpful = + if calculatedSize < declaredSizeBits + then + printf + "\nPerhaps you should add 'reserved(%d)' to the end of your register declaration?" + (declaredSizeBits - calculatedSize) + else "" + in tell + [ Diagnostic + Error + ( printf + "Calculated size %d does not match declared size %d.%s" + calculatedSize + declaredSizeBits + helpful + ) + (unCommented a) + ] + + if isUnion + then + checkUnion cursor (declaredSizeBits `div` 8) (s3 : returned) a + else + return (cursor + declaredSizeBits `div` 8, s3 : returned) + TypeSubStructure (Identity subBody) maybeIdent annot -> do + (newBody, size) <- + objTypeBodyToStage3 + st + subBody + ( if isUnion then startOff else cursor + ) + let s3 = mapDirected (const $ TypeSubStructure (Identity newBody) maybeIdent annot) decl + + checkTypesSubStructure subBody maybeIdent annot + if isUnion + then + checkUnion cursor size (s3 : returned) a + else + return (cursor + size, s3 : returned) + ReservedDecl expr annot -> do + size' <- fromIntegral <$> exprToSize expr + when ((size' `mod` 8) /= 0) $ + tell + [ Diagnostic + Error + "Can only reserve a multiple of 8 bits in this context." + (unCommented a) + ] + + expr' <- advanceStage st expr + let size = size' `div` 8 + let s3 = mapDirected (const $ ReservedDecl expr' annot) decl + if isUnion + then + checkUnion cursor size (s3 : returned) a + else + return (cursor + size, s3 : returned) + AssertPosStatement _ expr a -> do + declaredPos <- fromIntegral <$> exprToSize expr + + let expectedPos = if isUnion then startOff else cursor + startOff + + when (expectedPos /= declaredPos) $ do + tell + [ Diagnostic + Error + ( printf + "Position assertion failed. Asserted 0x%x, calculated 0x%x" + declaredPos + expectedPos + ) + (unCommented a) + ] + return (cursor, returned) + ) + (0, []) + decls + + return (ObjTypeBody bodyType (reverse returned) a, cur) + where + checkTypesSubStructure + (ObjTypeBody bodyType decls _) + maybeIdent + annot = + let emitWarning s = tell [Diagnostic Warning s (unCommented annot)] + in case () of + () + | [_] <- decls, + (Union {}) <- bodyType -> + emitWarning "Union with a single field. Should this be a struct?" + () + | [_] <- decls, + (Struct {}) <- bodyType, + Nothing <- maybeIdent -> + emitWarning "Anonymous sub-struct with single field is superfluous." + () + | [] <- decls -> + emitWarning + ( printf + "Empty sub-%s is superfluous." + ( case bodyType of + Union {} -> "union" + Struct {} -> "struct" + ) + ) + _ -> return () + fUnzip xs = (fst <$> xs, snd <$> xs) + pushApply :: Maybe (a, b) -> (Maybe a, Maybe b) + pushApply (Just (a, b)) = (Just a, Just b) + pushApply Nothing = (Nothing, Nothing) + +registerBodyToStage3 :: + LocalState -> + RegisterBody Expanded I Annot -> + Compile GlobalState (RegisterBody Checked I Annot, Word32) +registerBodyToStage3 + st + (RegisterBody bodyType (Identity deferredRegisterBody) a') = do + let isUnion = case bodyType of + Union {} -> True + _ -> False + + case deferredRegisterBody of + DeferredRegisterBody decls a -> do + (cur, returned) <- + foldlM + ( \(cursor, returned) decl -> + case undirected decl of + ReservedBits expr a -> do + size <- fromIntegral <$> exprToSize expr + expr' <- advanceStage st expr + let s3 = + mapDirected + (const $ ReservedBits expr' a) + decl + if isUnion + then checkUnion cursor size (s3 : returned) a + else + return (cursor + size, s3 : returned) + BitsSubStructure registerBody maybeIdent annot -> do + checkBitsSubStructure registerBody maybeIdent annot + + (newBody, subsize) <- registerBodyToStage3 st registerBody + let s3 = + mapDirected + (const $ BitsSubStructure newBody maybeIdent annot) + decl + + if isUnion + then checkUnion cursor subsize (s3 : returned) a + else + return (cursor + subsize, s3 : returned) + DefinedBits modifier identifier typeref a -> do + (s3TypeRef, size) <- registerBitsTypeRefToStage3 st typeref + let s3 = + mapDirected + (const $ DefinedBits modifier identifier s3TypeRef a) + decl + + if isUnion + then checkUnion cursor size (s3 : returned) a + else + return (cursor + size, s3 : returned) + ) + (0, []) + decls + + return + ( RegisterBody + bodyType + (Identity (DeferredRegisterBody (reverse returned) a)) + a', + cur + ) + where + checkBitsSubStructure + (RegisterBody bodyType (Identity (DeferredRegisterBody decls _)) _) + maybeIdent + annot = + let emitWarning s = tell [Diagnostic Warning s (unCommented annot)] + in case () of + () + | [_] <- decls, + (Union {}) <- bodyType -> + emitWarning "Union with a single field. Should this be a struct?" + () + | [_] <- decls, + (Struct {}) <- bodyType, + Nothing <- maybeIdent -> + emitWarning "Anonymous sub-struct with single field is superfluous." + () + | [] <- decls -> + emitWarning + ( printf + "Empty sub-%s is superfluous." + ( case bodyType of + Union {} -> "union" + Struct {} -> "struct" + ) + ) + _ -> return () + +registerBitsTypeRefToStage3 :: + LocalState -> + RegisterBitsTypeRef Expanded I Annot -> + Compile GlobalState (RegisterBitsTypeRef Checked I Annot, Word32) +registerBitsTypeRefToStage3 localState = \case + RegisterBitsArray ref expr a -> do + (ref', size) <- registerBitsTypeRefToStage3 localState ref + multiplier <- exprToSize expr + expr' <- advanceStage localState expr + return + ( RegisterBitsArray ref' expr' a, + size * fromIntegral multiplier + ) + RegisterBitsReference name a -> + (RegisterBitsReference name a,) <$> lookupTypeSize localState name + RegisterBitsJustBits expr a -> do + expr' <- advanceStage localState expr + (RegisterBitsJustBits expr' a,) + . fromIntegral + <$> exprToSize expr + +checkUnion :: Word32 -> Word32 -> b -> Commented SourceSpan -> Compile s (Word32, b) +checkUnion cursor subsize ret a = do + when (cursor /= 0 && subsize /= cursor) $ do + tell + [ Diagnostic + Warning + ( printf + "Jagged union found. Found size %d, expected %d.\n \ + \ Please wrap smaller fields in a struct with padding so all \ + \ fields are the same size?" + subsize + cursor + ) + (unCommented a) + ] + return (max cursor subsize, ret) + +exprToSize :: + (NumberType stage ~ Integer) => + Expression stage I Annot -> + Compile s Integer +exprToSize (LitNum num _) = return num +exprToSize e = do + tell [Diagnostic Error "Variables not allowed" (unCommented $ annot e)] + compilationFailure + +lookupTypeSize :: LocalState -> Name I Annot -> Compile GlobalState SizeBits +lookupTypeSize (LocalState scopePath) (Name idents a) = do + -- Convert the list of identifiers to a string path + let path = fmap (\(Identifier s _) -> Text.unpack s) idents + + -- Get the current scope and perform the lookup + results <- gets $ lookupScopeWithPath scopePath path . globalScope + + case results of + -- Successfully resolved to a unique size + [(_, Right sz)] -> return sz + -- Multiple ambiguous results found + matches@(_ : _) -> do + -- Generate a list of ambiguous paths for error reporting + let ambiguousPaths = + map + ( \(resolvedPath, _) -> + intercalate "." (NonEmpty.toList resolvedPath) + ) + matches + tell + [ Diagnostic + Error + ( printf + "Ambiguous occurrence of '%s'. Multiple matches found:\n%s" + (intercalate "." $ NonEmpty.toList path) + (unlines ambiguousPaths) -- List all ambiguous paths + ) + (unCommented a) + ] + compilationFailure + + -- No matches found + _ -> do + tell + [ Diagnostic + Error + ( printf + "Cannot resolve '%s'. No matching symbols found." + (intercalate "." $ NonEmpty.toList path) + ) + (unCommented a) + ] + compilationFailure + +getTypeSize :: BitType Expanded I Annot -> Compile s SizeBits +getTypeSize (RawBits expr _) = fromIntegral <$> exprToSize expr +getTypeSize (EnumBitType expr (Identity (EnumBody constants _)) ann) = do + declaredSize <- fromIntegral <$> exprToSize expr + + -- If the declared size is less than or equal to 4, we'll enforce that the + -- enum is packed. This is to make sure the user has covered all bases. + when (declaredSize <= 4) $ do + imap <- + foldlM + ( \imap (undirected -> enumConst) -> do + number <- case enumConst of + EnumConstantDecl _ expr _ -> exprToSize expr + EnumConstantReserved expr _ -> exprToSize expr + + when (number >= 2 ^ declaredSize) $ + tell + [ Diagnostic + Error + ( printf + "Enum constant too large. Max allowed %d\n" + ((2 :: Int) ^ declaredSize) + ) + (unCommented (annot enumConst)) + ] + + return $ IntMap.insert (fromIntegral number) True imap + ) + IntMap.empty + constants + let missing = + filter (not . (`IntMap.member` imap)) [0 .. 2 ^ declaredSize - 1] + unless (null missing) $ + tell + [ Diagnostic + Warning + ( printf + "Missing enum constants %s. Small enums should be fully \ + \ populated. Use 'reserved' if needed." + (intercalate ", " (map show missing)) + ) + (unCommented ann) + ] + + return declaredSize + +diagnosticError :: String -> Annot -> Compile a () +diagnosticError str a = tell [Diagnostic Error str (unCommented a)] + +insertTypeSize :: + LocalState -> + Identifier f Annot -> + SizeBits -> + Compile GlobalState () +insertTypeSize (LocalState scopePath) (Identifier s annot) size = do + modifyM $ + \(GlobalState globalScope) -> + let fullName = + NonEmpty.prependList + (currentScope scopePath) + (NonEmpty.singleton (Text.unpack s)) + in case upsertScope fullName (Right size) globalScope of + (Just _, _) -> do + diagnosticError (printf "Duplicate type %s" s) annot + compilationFailure + (Nothing, n) -> return $ GlobalState n + where + modifyM fn = do + s <- get + put =<< fn s diff --git a/src/Language/Fiddle/Compiler/Expansion.hs b/src/Language/Fiddle/Compiler/Expansion.hs new file mode 100644 index 0000000..8cfd0f0 --- /dev/null +++ b/src/Language/Fiddle/Compiler/Expansion.hs @@ -0,0 +1,230 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE IncoherentInstances #-} +{-# LANGUAGE UndecidableInstances #-} + +module Language.Fiddle.Compiler.Expansion (expandAst) where + +import Control.Monad.Identity (Identity (..)) +import Control.Monad.State (get, gets, modify, put) +import qualified Data.Char as Char +import Data.List (intercalate) +import qualified Data.List.NonEmpty as NonEmpty +import Data.Text (Text) +import qualified Data.Text as Text +import Data.Type.Bool +import Debug.Trace +import GHC.TypeLits +import Language.Fiddle.Ast +import Language.Fiddle.Compiler +import Language.Fiddle.Types +import Text.Printf (printf) + +type Annot = Commented SourceSpan + +newtype Path = Path [PathExpression] + +newtype PathExpression = PathExpression String + +type M = Compile State + +joinPath :: Path -> String +joinPath (Path l) = intercalate "#" $ reverse (map (\(PathExpression s) -> s) l) + +expandAst :: FiddleUnit Parsed I Annot -> Compile () (FiddleUnit Expanded I Annot) +expandAst = fmap snd . subCompile (State [] []) . advanceStage (Path mempty) + +-- Shorthand for Identity +type I = Identity + +newtype Linkage = Linkage Text deriving (Show) + +data State + = State + -- Anonymous object type bodies that need to be re-linked + ![(Linkage, ObjTypeBody Expanded I Annot)] + -- Anonymous enum bodies that need to be re-linked + ![(Linkage, AnonymousBitsType Expanded I Annot)] + +instance CompilationStage Parsed where + type StageAfter Parsed = Expanded + type StageMonad Parsed = M + type StageState Parsed = Path + type StageFunctor Parsed = Identity + type StageAnnotation Parsed = Annot + +deriving instance AdvanceStage Parsed ObjTypeBody + +deriving instance AdvanceStage Parsed DeferredRegisterBody + +deriving instance AdvanceStage Parsed RegisterBody + +deriving instance AdvanceStage Parsed AnonymousBitsType + +deriving instance AdvanceStage Parsed BitType + +deriving instance AdvanceStage Parsed EnumBody + +deriving instance AdvanceStage Parsed EnumConstantDecl + +deriving instance (AdvanceStage Parsed t) => AdvanceStage Parsed (Directed t) + +instance AdvanceStage Parsed RegisterBitsDecl where + modifyState t = + return + . case t of + DefinedBits {definedBitsIdent = i} -> pushId i + _ -> id + +instance AdvanceStage Parsed PackageBody where + advanceStage p (PackageBody decls a) = + PackageBody <$> reconfigureFiddleDecls p decls <*> pure a + +instance AdvanceStage Parsed ObjTypeDecl where + modifyState t = + return + . case t of + TypeSubStructure {subStructureName = (Just n)} -> pushId n + RegisterDecl {regIdent = (Just n)} -> pushId n + _ -> id + +instance AdvanceStage Parsed FiddleDecl where + modifyState t = + return + . case t of + PackageDecl {packageName = n} -> pushName n + BitsDecl {bitsIdent = i} -> pushId i + ObjTypeDecl {objTypeIdent = i} -> pushId i + ObjectDecl {objectIdent = i} -> pushId i + _ -> id + +instance AdvanceStage Parsed FiddleUnit where + advanceStage path (FiddleUnit decls a) = + FiddleUnit <$> reconfigureFiddleDecls path decls <*> pure a + +instance AdvanceStage Parsed Expression where + advanceStage _ = \case + (Var i a) -> return $ Var i a + (LitNum t a) -> LitNum <$> parseNum (unCommented a) t <*> pure a + +instance AdvanceStage Parsed RegisterBitsTypeRef where + advanceStage path = \case + RegisterBitsArray typeref expr annot -> + RegisterBitsArray + <$> advanceStage path typeref + <*> advanceStage path expr + <*> pure annot + RegisterBitsReference name annot -> + return $ RegisterBitsReference name annot + RegisterBitsJustBits expr annot -> + RegisterBitsJustBits + <$> advanceStage path expr + <*> pure annot + RegisterBitsAnonymousType _ anonType annot -> do + ident <- + internAnonymousBitsType path + =<< advanceStage path anonType + return $ RegisterBitsReference (identToName ident) annot + +instance AdvanceStage Parsed ObjType where + advanceStage path = \case + (AnonymousObjType _ (Identity body) annot) -> do + body' <- advanceStage path body + identifier <- internObjType path body' + return (ReferencedObjType (identToName identifier) annot) + (ReferencedObjType name annot) -> + return $ ReferencedObjType name annot + (ArrayObjType objType expr a) -> + ArrayObjType + <$> advanceStage path objType + <*> advanceStage path expr + <*> pure a + +parseNum :: SourceSpan -> Text -> Compile s Integer +parseNum span txt = fromMayberOrFail span "Unable to parse number" $ + case Text.unpack (Text.take 2 txt) of + "0b" -> toNumWithRadix (Text.drop 2 txt) 2 + "0x" -> toNumWithRadix (Text.drop 2 txt) 16 + ('0' : _) -> toNumWithRadix (Text.tail txt) 8 + _ -> toNumWithRadix txt 10 + where + removeUnders :: Text -> Text + removeUnders = Text.replace (Text.pack "_") Text.empty + + toNumWithRadix :: Text -> Int -> Maybe Integer + toNumWithRadix (removeUnders -> txt) (fromIntegral -> radix) = + Text.foldl + ( \mAcc x -> + mAcc >>= (\acc -> (acc * radix +) <$> digitToInt x radix) + ) + (Just 0) + txt + + digitToInt :: Char -> Integer -> Maybe Integer + digitToInt (Char.toLower -> ch) radix = + let a + | Char.isDigit ch = Just (Char.ord ch - Char.ord '0') + | ch >= 'a' && ch <= 'f' = Just $ (Char.ord ch - Char.ord 'a') + 10 + | otherwise = Nothing + in a + >>= ( \a' -> + if a' >= fromIntegral radix + then Nothing + else Just (fromIntegral a') + ) + +reconfigureFiddleDecls :: + Path -> + [Directed FiddleDecl Parsed I Annot] -> + M [Directed FiddleDecl Expanded I Annot] +reconfigureFiddleDecls p decls = do + lastState <- get + put (State [] []) + decls <- mapM (mapDirectedM $ advanceStage p) decls + (State anonymousObjTypes anonymousBitsTypes) <- get + put lastState + + return $ + map (asDirected . resolveAnonymousObjType) anonymousObjTypes + ++ map (asDirected . resolveAnonymousBitsType) anonymousBitsTypes + ++ decls + where + resolveAnonymousObjType (Linkage linkage, objTypeBody) = + ObjTypeDecl + (Identifier linkage (annot objTypeBody)) + (pure objTypeBody) + (annot objTypeBody) + + resolveAnonymousBitsType (Linkage linkage, AnonymousEnumBody expr body a) = + BitsDecl (Identifier linkage a) (EnumBitType expr body a) a + +identToName :: Identifier I a -> Name I a +identToName ident = Name (NonEmpty.singleton ident) (annot ident) + +internObjType :: Path -> ObjTypeBody Expanded I Annot -> M (Identifier I Annot) +internObjType path body = + let str = Text.pack $ joinPath path + in do + modify $ \(State objTypeBodies a) -> + State ((Linkage str, body) : objTypeBodies) a + return (Identifier str (annot body)) + +internAnonymousBitsType :: + Path -> + AnonymousBitsType Expanded I Annot -> + M (Identifier I Annot) +internAnonymousBitsType path anonymousBitsType = + let str = Text.pack $ joinPath path + in do + modify $ \(State a anonymousBitsTypes) -> + State a ((Linkage str, anonymousBitsType) : anonymousBitsTypes) + return (Identifier str (annot anonymousBitsType)) + +pushId :: Identifier f a -> Path -> Path +pushId (Identifier str _) (Path lst) = + Path (PathExpression (Text.unpack str) : lst) + +pushName :: Name f a -> Path -> Path +pushName (Name idents _) path = + foldl (flip pushId) path idents diff --git a/src/Language/Fiddle/Compiler/Stage0.hs b/src/Language/Fiddle/Compiler/Stage0.hs index fbc554b..96ee539 100644 --- a/src/Language/Fiddle/Compiler/Stage0.hs +++ b/src/Language/Fiddle/Compiler/Stage0.hs @@ -15,7 +15,7 @@ newtype Stage0Diagnostic = SyntaxError String toStage0 :: String -> Data.Text.Text -> - Compile () (FiddleUnit Stage1 (Either ParseError) (Commented SourceSpan)) + Compile () (FiddleUnit Parsed (Either ParseError) (Commented SourceSpan)) toStage0 filePath text = case Language.Fiddle.Parser.parseFiddleText filePath text of Left pe -> do @@ -23,13 +23,13 @@ toStage0 filePath text = compilationFailure Right a -> return a --- Gets the AST ready for Stage1 processing .This will report primarily +-- Gets the AST ready for Parsed processing .This will report primarily -- SyntaxErrors and errors parsing the tree. -- -- In the process, the tree is un-deferred and all parts of the toStage1 :: - FiddleUnit Stage1 (Either ParseError) a -> - Compile () (FiddleUnit Stage1 Identity a) + FiddleUnit Parsed (Either ParseError) a -> + Compile () (FiddleUnit Parsed Identity a) toStage1 ast = do alter ( \case diff --git a/src/Language/Fiddle/Compiler/Stage1.hs b/src/Language/Fiddle/Compiler/Stage1.hs deleted file mode 100644 index aae80e4..0000000 --- a/src/Language/Fiddle/Compiler/Stage1.hs +++ /dev/null @@ -1,230 +0,0 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE IncoherentInstances #-} -{-# LANGUAGE UndecidableInstances #-} - -module Language.Fiddle.Compiler.Stage1 (toStage2) where - -import Control.Monad.Identity (Identity (..)) -import Control.Monad.State (get, gets, modify, put) -import qualified Data.Char as Char -import Data.List (intercalate) -import qualified Data.List.NonEmpty as NonEmpty -import Data.Text (Text) -import qualified Data.Text as Text -import Data.Type.Bool -import Debug.Trace -import GHC.TypeLits -import Language.Fiddle.Ast -import Language.Fiddle.Compiler -import Language.Fiddle.Types -import Text.Printf (printf) - -type Annot = Commented SourceSpan - -newtype Path = Path [PathExpression] - -newtype PathExpression = PathExpression String - -type M = Compile State - -joinPath :: Path -> String -joinPath (Path l) = intercalate "#" $ reverse (map (\(PathExpression s) -> s) l) - -toStage2 :: FiddleUnit Stage1 I Annot -> Compile () (FiddleUnit Stage2 I Annot) -toStage2 = fmap snd . subCompile (State [] []) . advanceStage (Path mempty) - --- Shorthand for Identity -type I = Identity - -newtype Linkage = Linkage Text deriving (Show) - -data State - = State - -- Anonymous object type bodies that need to be re-linked - ![(Linkage, ObjTypeBody Stage2 I Annot)] - -- Anonymous enum bodies that need to be re-linked - ![(Linkage, AnonymousBitsType Stage2 I Annot)] - -instance CompilationStage Stage1 where - type StageAfter Stage1 = Stage2 - type StageMonad Stage1 = M - type StageState Stage1 = Path - type StageFunctor Stage1 = Identity - type StageAnnotation Stage1 = Annot - -deriving instance AdvanceStage Stage1 ObjTypeBody - -deriving instance AdvanceStage Stage1 DeferredRegisterBody - -deriving instance AdvanceStage Stage1 RegisterBody - -deriving instance AdvanceStage Stage1 AnonymousBitsType - -deriving instance AdvanceStage Stage1 BitType - -deriving instance AdvanceStage Stage1 EnumBody - -deriving instance AdvanceStage Stage1 EnumConstantDecl - -deriving instance (AdvanceStage Stage1 t) => AdvanceStage Stage1 (Directed t) - -instance AdvanceStage Stage1 RegisterBitsDecl where - modifyState t = - return - . case t of - DefinedBits {definedBitsIdent = i} -> pushId i - _ -> id - -instance AdvanceStage Stage1 PackageBody where - advanceStage p (PackageBody decls a) = - PackageBody <$> reconfigureFiddleDecls p decls <*> pure a - -instance AdvanceStage Stage1 ObjTypeDecl where - modifyState t = - return - . case t of - TypeSubStructure {subStructureName = (Just n)} -> pushId n - RegisterDecl {regIdent = (Just n)} -> pushId n - _ -> id - -instance AdvanceStage Stage1 FiddleDecl where - modifyState t = - return - . case t of - PackageDecl {packageName = n} -> pushName n - BitsDecl {bitsIdent = i} -> pushId i - ObjTypeDecl {objTypeIdent = i} -> pushId i - ObjectDecl {objectIdent = i} -> pushId i - _ -> id - -instance AdvanceStage Stage1 FiddleUnit where - advanceStage path (FiddleUnit decls a) = - FiddleUnit <$> reconfigureFiddleDecls path decls <*> pure a - -instance AdvanceStage Stage1 Expression where - advanceStage _ = \case - (Var i a) -> return $ Var i a - (LitNum t a) -> LitNum <$> parseNum (unCommented a) t <*> pure a - -instance AdvanceStage Stage1 RegisterBitsTypeRef where - advanceStage path = \case - RegisterBitsArray typeref expr annot -> - RegisterBitsArray - <$> advanceStage path typeref - <*> advanceStage path expr - <*> pure annot - RegisterBitsReference name annot -> - return $ RegisterBitsReference name annot - RegisterBitsJustBits expr annot -> - RegisterBitsJustBits - <$> advanceStage path expr - <*> pure annot - RegisterBitsAnonymousType _ anonType annot -> do - ident <- - internAnonymousBitsType path - =<< advanceStage path anonType - return $ RegisterBitsReference (identToName ident) annot - -instance AdvanceStage Stage1 ObjType where - advanceStage path = \case - (AnonymousObjType _ (Identity body) annot) -> do - body' <- advanceStage path body - identifier <- internObjType path body' - return (ReferencedObjType (identToName identifier) annot) - (ReferencedObjType name annot) -> - return $ ReferencedObjType name annot - (ArrayObjType objType expr a) -> - ArrayObjType - <$> advanceStage path objType - <*> advanceStage path expr - <*> pure a - -parseNum :: SourceSpan -> Text -> Compile s Integer -parseNum span txt = fromMayberOrFail span "Unable to parse number" $ - case Text.unpack (Text.take 2 txt) of - "0b" -> toNumWithRadix (Text.drop 2 txt) 2 - "0x" -> toNumWithRadix (Text.drop 2 txt) 16 - ('0' : _) -> toNumWithRadix (Text.tail txt) 8 - _ -> toNumWithRadix txt 10 - where - removeUnders :: Text -> Text - removeUnders = Text.replace (Text.pack "_") Text.empty - - toNumWithRadix :: Text -> Int -> Maybe Integer - toNumWithRadix (removeUnders -> txt) (fromIntegral -> radix) = - Text.foldl - ( \mAcc x -> - mAcc >>= (\acc -> (acc * radix +) <$> digitToInt x radix) - ) - (Just 0) - txt - - digitToInt :: Char -> Integer -> Maybe Integer - digitToInt (Char.toLower -> ch) radix = - let a - | Char.isDigit ch = Just (Char.ord ch - Char.ord '0') - | ch >= 'a' && ch <= 'f' = Just $ (Char.ord ch - Char.ord 'a') + 10 - | otherwise = Nothing - in a - >>= ( \a' -> - if a' >= fromIntegral radix - then Nothing - else Just (fromIntegral a') - ) - -reconfigureFiddleDecls :: - Path -> - [Directed FiddleDecl Stage1 I Annot] -> - M [Directed FiddleDecl Stage2 I Annot] -reconfigureFiddleDecls p decls = do - lastState <- get - put (State [] []) - decls <- mapM (mapDirectedM $ advanceStage p) decls - (State anonymousObjTypes anonymousBitsTypes) <- get - put lastState - - return $ - map (asDirected . resolveAnonymousObjType) anonymousObjTypes - ++ map (asDirected . resolveAnonymousBitsType) anonymousBitsTypes - ++ decls - where - resolveAnonymousObjType (Linkage linkage, objTypeBody) = - ObjTypeDecl - (Identifier linkage (annot objTypeBody)) - (pure objTypeBody) - (annot objTypeBody) - - resolveAnonymousBitsType (Linkage linkage, AnonymousEnumBody expr body a) = - BitsDecl (Identifier linkage a) (EnumBitType expr body a) a - -identToName :: Identifier I a -> Name I a -identToName ident = Name (NonEmpty.singleton ident) (annot ident) - -internObjType :: Path -> ObjTypeBody Stage2 I Annot -> M (Identifier I Annot) -internObjType path body = - let str = Text.pack $ joinPath path - in do - modify $ \(State objTypeBodies a) -> - State ((Linkage str, body) : objTypeBodies) a - return (Identifier str (annot body)) - -internAnonymousBitsType :: - Path -> - AnonymousBitsType Stage2 I Annot -> - M (Identifier I Annot) -internAnonymousBitsType path anonymousBitsType = - let str = Text.pack $ joinPath path - in do - modify $ \(State a anonymousBitsTypes) -> - State a ((Linkage str, anonymousBitsType) : anonymousBitsTypes) - return (Identifier str (annot anonymousBitsType)) - -pushId :: Identifier f a -> Path -> Path -pushId (Identifier str _) (Path lst) = - Path (PathExpression (Text.unpack str) : lst) - -pushName :: Name f a -> Path -> Path -pushName (Name idents _) path = - foldl (flip pushId) path idents diff --git a/src/Language/Fiddle/Compiler/Stage2.hs b/src/Language/Fiddle/Compiler/Stage2.hs deleted file mode 100644 index adf5450..0000000 --- a/src/Language/Fiddle/Compiler/Stage2.hs +++ /dev/null @@ -1,514 +0,0 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE IncoherentInstances #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE UndecidableInstances #-} - -module Language.Fiddle.Compiler.Stage2 (toStage3) where - -import Control.Monad (forM, forM_, unless, when) -import Control.Monad.Identity (Identity (Identity)) -import Control.Monad.RWS (MonadState (get, put), MonadWriter (tell), gets, modify') -import Data.Foldable (Foldable (toList), foldlM) -import Data.Functor.Identity -import qualified Data.IntMap as IntMap -import Data.Kind (Type) -import Data.List (inits, intercalate) -import Data.List.NonEmpty (NonEmpty (..)) -import qualified Data.List.NonEmpty as NonEmpty -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Maybe (fromMaybe) -import qualified Data.Set as Set -import qualified Data.Text as Text -import Data.Word (Word32) -import Language.Fiddle.Ast -import Language.Fiddle.Compiler -import Language.Fiddle.Internal.Scopes -import Language.Fiddle.Types (Commented (unCommented), SourceSpan) -import Text.Printf (printf) -import Prelude hiding (unzip) - -newtype GlobalState = GlobalState - { globalScope :: Scope String (Either SizeBits SizeBytes) - } - -newtype LocalState = LocalState (ScopePath String) - -type I = Identity - -type Annot = Commented SourceSpan - -type SizeBits = Word32 - -type SizeBytes = Word32 - -toStage3 :: FiddleUnit Stage2 I Annot -> Compile () (FiddleUnit Stage3 I Annot) -toStage3 = fmap snd . subCompile (GlobalState mempty) . advanceStage (LocalState mempty) - -instance CompilationStage Stage2 where - type StageAfter Stage2 = Stage3 - type StageMonad Stage2 = Compile GlobalState - type StageState Stage2 = LocalState - type StageFunctor Stage2 = Identity - type StageAnnotation Stage2 = Commented SourceSpan - -deriving instance AdvanceStage Stage2 FiddleUnit - -deriving instance AdvanceStage Stage2 Expression - -deriving instance AdvanceStage Stage2 ObjType - -deriving instance AdvanceStage Stage2 DeferredRegisterBody - -deriving instance AdvanceStage Stage2 RegisterBitsDecl - -deriving instance AdvanceStage Stage2 RegisterBitsTypeRef - -deriving instance AdvanceStage Stage2 AnonymousBitsType - -deriving instance AdvanceStage Stage2 BitType - -deriving instance AdvanceStage Stage2 EnumBody - -deriving instance AdvanceStage Stage2 EnumConstantDecl - -deriving instance AdvanceStage Stage2 PackageBody - -deriving instance (AdvanceStage Stage2 t) => AdvanceStage Stage2 (Directed t) - -instance AdvanceStage Stage2 RegisterBody where - advanceStage s body = fst <$> registerBodyToStage3 s body - -instance AdvanceStage Stage2 ObjTypeBody where - advanceStage s body = fst <$> objTypeBodyToStage3 s body 0 - -instance AdvanceStage Stage2 FiddleDecl where - modifyState t s = case t of - (BitsDecl id typ a) -> do - typeSize <- getTypeSize typ - insertTypeSize s id typeSize - return s - (PackageDecl n _ _) -> do - let strs = nameToList n - let (LocalState scopePath) = s - - return $ - LocalState $ - scopePath {currentScope = strs ++ currentScope scopePath} - (UsingDecl n _) -> - let (LocalState scopePath) = s - in return $ - LocalState $ - scopePath - { usingPaths = nameToList n : usingPaths scopePath - } - _ -> return s - -nameToList :: Name f a -> [String] -nameToList (Name idents _) = map (\(Identifier (Text.unpack -> s) _) -> s) (toList idents) - -objTypeBodyToStage3 :: - LocalState -> - ObjTypeBody Stage2 I Annot -> - Word32 -> - Compile GlobalState (ObjTypeBody Stage3 I Annot, Word32) -objTypeBodyToStage3 st (ObjTypeBody bodyType decls a) startOff = do - let isUnion = case bodyType of - Union {} -> True - _ -> False - (cur, returned) <- - foldlM - ( \(cursor, returned) decl -> - case undirected decl of - RegisterDecl mMod mIdent expr mBody a -> do - (s3RegisterBody, mCalculatedSize) <- - fUnzip <$> mapM (registerBodyToStage3 st) mBody - - nExpr <- advanceStage st expr - - let s3 = - mapDirected - ( const $ - RegisterDecl - mMod - mIdent - nExpr - s3RegisterBody - a - ) - decl - - declaredSizeBits <- fromIntegral <$> exprToSize expr - - when ((declaredSizeBits `mod` 8) /= 0) $ - tell - [ Diagnostic - Error - "Register size is not a multiple of 8. Please pad register size to align with 8. " - (unCommented a) - ] - - forM_ mCalculatedSize $ \(fromIntegral -> calculatedSize) -> - unless (calculatedSize == declaredSizeBits) $ - let helpful = - if calculatedSize < declaredSizeBits - then - printf - "\nPerhaps you should add 'reserved(%d)' to the end of your register declaration?" - (declaredSizeBits - calculatedSize) - else "" - in tell - [ Diagnostic - Error - ( printf - "Calculated size %d does not match declared size %d.%s" - calculatedSize - declaredSizeBits - helpful - ) - (unCommented a) - ] - - if isUnion - then - checkUnion cursor (declaredSizeBits `div` 8) (s3 : returned) a - else - return (cursor + declaredSizeBits `div` 8, s3 : returned) - TypeSubStructure (Identity subBody) maybeIdent annot -> do - (newBody, size) <- - objTypeBodyToStage3 - st - subBody - ( if isUnion then startOff else cursor - ) - let s3 = mapDirected (const $ TypeSubStructure (Identity newBody) maybeIdent annot) decl - - checkTypesSubStructure subBody maybeIdent annot - if isUnion - then - checkUnion cursor size (s3 : returned) a - else - return (cursor + size, s3 : returned) - ReservedDecl expr annot -> do - size' <- fromIntegral <$> exprToSize expr - when ((size' `mod` 8) /= 0) $ - tell - [ Diagnostic - Error - "Can only reserve a multiple of 8 bits in this context." - (unCommented a) - ] - - expr' <- advanceStage st expr - let size = size' `div` 8 - let s3 = mapDirected (const $ ReservedDecl expr' annot) decl - if isUnion - then - checkUnion cursor size (s3 : returned) a - else - return (cursor + size, s3 : returned) - AssertPosStatement _ expr a -> do - declaredPos <- fromIntegral <$> exprToSize expr - - let expectedPos = if isUnion then startOff else cursor + startOff - - when (expectedPos /= declaredPos) $ do - tell - [ Diagnostic - Error - ( printf - "Position assertion failed. Asserted 0x%x, calculated 0x%x" - declaredPos - expectedPos - ) - (unCommented a) - ] - return (cursor, returned) - ) - (0, []) - decls - - return (ObjTypeBody bodyType (reverse returned) a, cur) - where - checkTypesSubStructure - (ObjTypeBody bodyType decls _) - maybeIdent - annot = - let emitWarning s = tell [Diagnostic Warning s (unCommented annot)] - in case () of - () - | [_] <- decls, - (Union {}) <- bodyType -> - emitWarning "Union with a single field. Should this be a struct?" - () - | [_] <- decls, - (Struct {}) <- bodyType, - Nothing <- maybeIdent -> - emitWarning "Anonymous sub-struct with single field is superfluous." - () - | [] <- decls -> - emitWarning - ( printf - "Empty sub-%s is superfluous." - ( case bodyType of - Union {} -> "union" - Struct {} -> "struct" - ) - ) - _ -> return () - fUnzip xs = (fst <$> xs, snd <$> xs) - pushApply :: Maybe (a, b) -> (Maybe a, Maybe b) - pushApply (Just (a, b)) = (Just a, Just b) - pushApply Nothing = (Nothing, Nothing) - -registerBodyToStage3 :: - LocalState -> - RegisterBody Stage2 I Annot -> - Compile GlobalState (RegisterBody Stage3 I Annot, Word32) -registerBodyToStage3 - st - (RegisterBody bodyType (Identity deferredRegisterBody) a') = do - let isUnion = case bodyType of - Union {} -> True - _ -> False - - case deferredRegisterBody of - DeferredRegisterBody decls a -> do - (cur, returned) <- - foldlM - ( \(cursor, returned) decl -> - case undirected decl of - ReservedBits expr a -> do - size <- fromIntegral <$> exprToSize expr - expr' <- advanceStage st expr - let s3 = - mapDirected - (const $ ReservedBits expr' a) - decl - if isUnion - then checkUnion cursor size (s3 : returned) a - else - return (cursor + size, s3 : returned) - BitsSubStructure registerBody maybeIdent annot -> do - checkBitsSubStructure registerBody maybeIdent annot - - (newBody, subsize) <- registerBodyToStage3 st registerBody - let s3 = - mapDirected - (const $ BitsSubStructure newBody maybeIdent annot) - decl - - if isUnion - then checkUnion cursor subsize (s3 : returned) a - else - return (cursor + subsize, s3 : returned) - DefinedBits modifier identifier typeref a -> do - (s3TypeRef, size) <- registerBitsTypeRefToStage3 st typeref - let s3 = - mapDirected - (const $ DefinedBits modifier identifier s3TypeRef a) - decl - - if isUnion - then checkUnion cursor size (s3 : returned) a - else - return (cursor + size, s3 : returned) - ) - (0, []) - decls - - return - ( RegisterBody - bodyType - (Identity (DeferredRegisterBody (reverse returned) a)) - a', - cur - ) - where - checkBitsSubStructure - (RegisterBody bodyType (Identity (DeferredRegisterBody decls _)) _) - maybeIdent - annot = - let emitWarning s = tell [Diagnostic Warning s (unCommented annot)] - in case () of - () - | [_] <- decls, - (Union {}) <- bodyType -> - emitWarning "Union with a single field. Should this be a struct?" - () - | [_] <- decls, - (Struct {}) <- bodyType, - Nothing <- maybeIdent -> - emitWarning "Anonymous sub-struct with single field is superfluous." - () - | [] <- decls -> - emitWarning - ( printf - "Empty sub-%s is superfluous." - ( case bodyType of - Union {} -> "union" - Struct {} -> "struct" - ) - ) - _ -> return () - -registerBitsTypeRefToStage3 :: - LocalState -> - RegisterBitsTypeRef Stage2 I Annot -> - Compile GlobalState (RegisterBitsTypeRef Stage3 I Annot, Word32) -registerBitsTypeRefToStage3 localState = \case - RegisterBitsArray ref expr a -> do - (ref', size) <- registerBitsTypeRefToStage3 localState ref - multiplier <- exprToSize expr - expr' <- advanceStage localState expr - return - ( RegisterBitsArray ref' expr' a, - size * fromIntegral multiplier - ) - RegisterBitsReference name a -> - (RegisterBitsReference name a,) <$> lookupTypeSize localState name - RegisterBitsJustBits expr a -> do - expr' <- advanceStage localState expr - (RegisterBitsJustBits expr' a,) - . fromIntegral - <$> exprToSize expr - -checkUnion :: Word32 -> Word32 -> b -> Commented SourceSpan -> Compile s (Word32, b) -checkUnion cursor subsize ret a = do - when (cursor /= 0 && subsize /= cursor) $ do - tell - [ Diagnostic - Warning - ( printf - "Jagged union found. Found size %d, expected %d.\n \ - \ Please wrap smaller fields in a struct with padding so all \ - \ fields are the same size?" - subsize - cursor - ) - (unCommented a) - ] - return (max cursor subsize, ret) - -exprToSize :: - (NumberType stage ~ Integer) => - Expression stage I Annot -> - Compile s Integer -exprToSize (LitNum num _) = return num -exprToSize e = do - tell [Diagnostic Error "Variables not allowed" (unCommented $ annot e)] - compilationFailure - -lookupTypeSize :: LocalState -> Name I Annot -> Compile GlobalState SizeBits -lookupTypeSize (LocalState scopePath) (Name idents a) = do - -- Convert the list of identifiers to a string path - let path = fmap (\(Identifier s _) -> Text.unpack s) idents - - -- Get the current scope and perform the lookup - results <- gets $ lookupScopeWithPath scopePath path . globalScope - - case results of - -- Successfully resolved to a unique size - [(_, Right sz)] -> return sz - -- Multiple ambiguous results found - matches@(_ : _) -> do - -- Generate a list of ambiguous paths for error reporting - let ambiguousPaths = - map - ( \(resolvedPath, _) -> - intercalate "." (NonEmpty.toList resolvedPath) - ) - matches - tell - [ Diagnostic - Error - ( printf - "Ambiguous occurrence of '%s'. Multiple matches found:\n%s" - (intercalate "." $ NonEmpty.toList path) - (unlines ambiguousPaths) -- List all ambiguous paths - ) - (unCommented a) - ] - compilationFailure - - -- No matches found - _ -> do - tell - [ Diagnostic - Error - ( printf - "Cannot resolve '%s'. No matching symbols found." - (intercalate "." $ NonEmpty.toList path) - ) - (unCommented a) - ] - compilationFailure - -getTypeSize :: BitType Stage2 I Annot -> Compile s SizeBits -getTypeSize (RawBits expr _) = fromIntegral <$> exprToSize expr -getTypeSize (EnumBitType expr (Identity (EnumBody constants _)) ann) = do - declaredSize <- fromIntegral <$> exprToSize expr - - -- If the declared size is less than or equal to 4, we'll enforce that the - -- enum is packed. This is to make sure the user has covered all bases. - when (declaredSize <= 4) $ do - imap <- - foldlM - ( \imap (undirected -> enumConst) -> do - number <- case enumConst of - EnumConstantDecl _ expr _ -> exprToSize expr - EnumConstantReserved expr _ -> exprToSize expr - - when (number >= 2 ^ declaredSize) $ - tell - [ Diagnostic - Error - ( printf - "Enum constant too large. Max allowed %d\n" - ((2 :: Int) ^ declaredSize) - ) - (unCommented (annot enumConst)) - ] - - return $ IntMap.insert (fromIntegral number) True imap - ) - IntMap.empty - constants - let missing = - filter (not . (`IntMap.member` imap)) [0 .. 2 ^ declaredSize - 1] - unless (null missing) $ - tell - [ Diagnostic - Warning - ( printf - "Missing enum constants %s. Small enums should be fully \ - \ populated. Use 'reserved' if needed." - (intercalate ", " (map show missing)) - ) - (unCommented ann) - ] - - return declaredSize - -diagnosticError :: String -> Annot -> Compile a () -diagnosticError str a = tell [Diagnostic Error str (unCommented a)] - -insertTypeSize :: LocalState -> Identifier f Annot -> SizeBits -> Compile GlobalState () -insertTypeSize (LocalState scopePath) (Identifier s annot) size = do - modifyM $ - \(GlobalState globalScope) -> - let fullName = - NonEmpty.prependList - (currentScope scopePath) - (NonEmpty.singleton (Text.unpack s)) - in case upsertScope fullName (Right size) globalScope of - (Just _, _) -> do - diagnosticError (printf "Duplicate type %s" s) annot - compilationFailure - (Nothing, n) -> return $ GlobalState n - where - modifyM fn = do - s <- get - put =<< fn s diff --git a/src/Language/Fiddle/Parser.hs b/src/Language/Fiddle/Parser.hs index 980925f..b3ed09a 100644 --- a/src/Language/Fiddle/Parser.hs +++ b/src/Language/Fiddle/Parser.hs @@ -29,7 +29,7 @@ type P = ParsecT S () Identity type A = Commented SourceSpan -type Pa (a :: Stage -> (Type -> Type) -> Type -> Type) = P (a 'Stage1 F (Commented SourceSpan)) +type Pa (a :: Stage -> (Type -> Type) -> Type -> Type) = P (a 'Parsed F (Commented SourceSpan)) type PaS (a :: (Type -> Type) -> Type -> Type) = P (a F (Commented SourceSpan)) @@ -52,7 +52,7 @@ isComment (Token t _) = stripTrailingComments :: [Token s] -> [Token s] stripTrailingComments = reverse . dropWhile isComment . reverse -directedP :: Pa t -> PaS (Directed t 'Stage1) +directedP :: Pa t -> PaS (Directed t 'Parsed) directedP subparser = withMeta $ do Directed <$> many directiveP <*> subparser @@ -164,7 +164,7 @@ objTypeP = do recur' <- recur return $ recur' base where - recur :: P (ObjType Stage1 F A -> ObjType Stage1 F A) + recur :: P (ObjType Parsed F A -> ObjType Parsed F A) recur = ( do withMeta $ do @@ -174,7 +174,7 @@ objTypeP = do ) <|> return id - baseObjP :: P (A -> ObjType Stage1 F A) + baseObjP :: P (A -> ObjType Parsed F A) baseObjP = (ReferencedObjType <$> name) <|> ( do @@ -262,7 +262,7 @@ registerBitsTypeRefP = do recur' <- recurP return (recur' base) where - recurP :: P (RegisterBitsTypeRef Stage1 F A -> RegisterBitsTypeRef Stage1 F A) + recurP :: P (RegisterBitsTypeRef Parsed F A -> RegisterBitsTypeRef Parsed F A) recurP = ( do withMeta $ do @@ -421,7 +421,7 @@ tok t' = do (\(Token _ (SourceSpan s1 _)) -> s1) (\tok@(Token t _) -> if t == t' then Just tok else Nothing) -parseFiddleText :: String -> Text -> F (FiddleUnit 'Stage1 F (Commented SourceSpan)) +parseFiddleText :: String -> Text -> F (FiddleUnit 'Parsed F (Commented SourceSpan)) parseFiddleText sourceName txt = runIdentity . Text.Parsec.runParserT diff --git a/src/Main.hs b/src/Main.hs index 6fba502..cf33e62 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -13,8 +13,8 @@ import GHC.IO.Exception (ExitCode (ExitFailure, ExitSuccess)) import Language.Fiddle.Ast import Language.Fiddle.Compiler (coloredFormat, compile_, printDiagnostic) import Language.Fiddle.Compiler.Stage0 -import Language.Fiddle.Compiler.Stage1 -import Language.Fiddle.Compiler.Stage2 +import Language.Fiddle.Compiler.Expansion +import Language.Fiddle.Compiler.ConsistencyCheck import Language.Fiddle.GenericTree (GenericSyntaxTree (..), ToGenericSyntaxTree (toGenericSyntaxTree), alterGenericSyntaxTree) import qualified Language.Fiddle.Parser import qualified Language.Fiddle.Tokenizer @@ -28,7 +28,7 @@ main = do case argv of [filePath] -> do text <- Data.Text.IO.readFile filePath - let (diags, ma) = compile_ $ toStage3 =<< toStage2 =<< toStage1 =<< toStage0 filePath text + let (diags, ma) = compile_ $ checkConsistency =<< expandAst =<< toStage1 =<< toStage0 filePath text ec <- case ma of Just ast -> do -- cgit