packages feed

language-wgsl (empty) → 0.1.0.0

raw patch · 6 files changed

+2489/−0 lines, 6 filesdep +basedep +language-wgsldep +prettyprinter

Dependencies added: base, language-wgsl, prettyprinter, tasty, tasty-hunit, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for language-wgsl++## 0.1.0.0 -- 2026-11-03++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright (c) 2024-2026 Sebastian Paarmann and the University of Copenhagen++Permission to use, copy, modify, and/or distribute this software for any purpose+with or without fee is hereby granted, provided that the above copyright notice+and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF+THIS SOFTWARE.
+ README.md view
@@ -0,0 +1,6 @@+# language-wgsl++A simple Haskell package for representing [WGSL](https://www.w3.org/TR/WGSL),+intended to closely match the specification. This package does not currently+include a parser, and also does not represent purely syntactic details (such as+whether an integer is hex or decimal).
+ language-wgsl.cabal view
@@ -0,0 +1,58 @@+cabal-version:      3.4+name:               language-wgsl+version:            0.1.0.0+synopsis:           AST, prettyprinter, and other tools for the WebGPU Shading Language.++description:        A package for working with the+                    <https://www.w3.org/TR/WGSL/ WebGPU Shading Language>+                    (WGSL).+                    .+                    The package is currently fairly minimalistic, and mostly+                    useful for programs that want to generate WGSL.++license:            ISC+license-file:       LICENSE+author:             Sebastian Paarmann+maintainer:         Troels Henriksen <athas@sigkill.dk>+copyright:          Sebastian Paarmann and the University of Copenhagen+category:           Language+homepage:           https://github.com/diku-dk/language-wgsl+bug-reports:        https://github.com/diku-dk/language-wgsl/issues+build-type:         Simple+tested-with:        GHC == 9.10.*, GHC == 9.12.*, GHC == 9.14.*+extra-doc-files:    CHANGELOG.md+                    README.md++source-repository head+    type:     git+    location: https://github.com/diku-dk/language-wgsl.git++common warnings+    ghc-options: -Wall -Wcompat++library+    import:           warnings+    exposed-modules:  Language.WGSL+    build-depends:    base >=4.15 && <5,+                      text >=1.2 && <2.2,+                      prettyprinter >=1.7 && <1.8+    hs-source-dirs:   src+    default-language: GHC2021+    default-extensions:+      OverloadedStrings++test-suite language-wgsl-test+    import:           warnings+    default-language: GHC2021+    default-extensions:+      OverloadedStrings+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          Test.hs+    build-depends:+        base >=4.15 && <5,+        text >=1.2 && <2.2,+        prettyprinter >=1.7 && <1.8,+        language-wgsl,+        tasty >=1.4 && <1.6,+        tasty-hunit >=0.10 && <0.11
+ src/Language/WGSL.hs view
@@ -0,0 +1,1329 @@+-- | Basic WGSL definitions, including AST definitions. These are intended to+-- correspond closely with the terminology and structure of the WGSL+-- specification, but it is not automatically generated from the specification,+-- and so may have inconsistencies. This module is intended to be imported+-- qualified:+--+-- @+-- import Language.WGSL qualified as WGSL+-- @+--+-- The definitions here are abstract in the sense that they cannot represent+-- syntactic details of a WGSL program, e.g., whether an integer literal is+-- written hexadecimally. This makes these definitions less useful for parsing,+-- but fine for code generation. I would not be opposed to adding a concrete+-- syntax module in the future.+--+-- To produce WGSL code as text, use the 'Pretty' instance for the various+-- constructs.+--+-- Known and intentional deviations from the specification:+--+-- 1. Statements do not have attributes+--+-- 2. Assignment targets are ordinary expressions, not a separate+--    @lhs_expression@.+--+-- 3. When printed, expressions are always fully parenthesised, so operator+--    precedence is not modelled.+module Language.WGSL+  ( -- * Identifiers+    Ident,+    validIdent,+    keywords,+    reservedWords,++    -- * Types+    Type (..),+    TextureDim (..),+    StorageTextureDim (..),+    DepthTexture (..),+    TexelFormat (..),++    -- * Memory+    AccessMode (..),+    AddressSpace (..),++    -- ** Alignment and size+    roundUp,+    roundDown,+    alignOf,+    sizeOf,+    strideOf,+    hostShareable,+    alignOfMember,+    sizeOfMember,+    alignOfStruct,+    offsetOfMembers,+    sizeOfStruct,++    -- * Expressions+    Expression (..),+    BinaryOperator (..),+    UnaryOperator (..),+    IntLiteralType (..),+    FloatLiteralType (..),+    intExp,+    floatExp,+    bitcast,++    -- * Statements+    Statement (..),+    AssignmentOperator (..),+    ForUpdate (..),+    SwitchClause (..),+    CaseSelector (..),+    Continuing (..),++    -- * Declarations+    Attribute (..),+    bindingAttribs,+    Param (..),+    FunctionDecl (..),+    StructMember (..),+    StructDecl (..),+    GlobalDecl (..),++    -- * Modules+    Module (..),+    Directive (..),+    Severity (..),+    EnableExtension (..),+    LanguageExtension (..),+  )+where++import Data.Char (GeneralCategory (..), generalCategory, isAlpha, isDigit)+import Data.List.NonEmpty (NonEmpty)+import Data.List.NonEmpty qualified as NE+import Data.Text qualified as T+import Prettyprinter++-- | A WGSL identifier, which is not checked for validity.+type Ident = T.Text++-- | Whether a name is a valid WGSL identifier. See+-- https://www.w3.org/TR/WGSL/#identifiers.+validIdent :: Ident -> Bool+validIdent t =+  matchesPattern+    && not ("__" `T.isPrefixOf` t)+    && t `notElem` keywords+    && t `notElem` reservedWords+  where+    matchesPattern = case T.uncons t of+      Nothing -> False+      Just (c, rest)+        | T.null rest -> xidStart c+        | otherwise -> (c == '_' || xidStart c) && T.all xidContinue rest++-- | Approximates the Unicode @XID_Start@ property.+xidStart :: Char -> Bool+xidStart c = isAlpha c || generalCategory c == LetterNumber++-- | Approximates the Unicode @XID_Continue@ property.+xidContinue :: Char -> Bool+xidContinue c =+  xidStart c+    || isDigit c+    || generalCategory c+      `elem` [NonSpacingMark, SpacingCombiningMark, ConnectorPunctuation]++-- | The WGSL keywords. See https://www.w3.org/TR/WGSL/#keyword-summary.+keywords :: [Ident]+keywords =+  [ "alias",+    "break",+    "case",+    "const",+    "const_assert",+    "continue",+    "continuing",+    "default",+    "diagnostic",+    "discard",+    "else",+    "enable",+    "false",+    "fn",+    "for",+    "if",+    "let",+    "loop",+    "override",+    "requires",+    "return",+    "struct",+    "switch",+    "true",+    "var",+    "while"+  ]++-- | The WGSL reserved words. See https://www.w3.org/TR/WGSL/#reserved-words.+reservedWords :: [Ident]+reservedWords =+  [ "NULL",+    "Self",+    "abstract",+    "active",+    "alignas",+    "alignof",+    "as",+    "asm",+    "asm_fragment",+    "async",+    "attribute",+    "auto",+    "await",+    "become",+    "cast",+    "catch",+    "class",+    "co_await",+    "co_return",+    "co_yield",+    "coherent",+    "column_major",+    "common",+    "compile",+    "compile_fragment",+    "concept",+    "const_cast",+    "consteval",+    "constexpr",+    "constinit",+    "crate",+    "debugger",+    "decltype",+    "delete",+    "demote",+    "demote_to_helper",+    "do",+    "dynamic_cast",+    "enum",+    "explicit",+    "export",+    "extends",+    "extern",+    "external",+    "fallthrough",+    "filter",+    "final",+    "finally",+    "friend",+    "from",+    "fxgroup",+    "get",+    "goto",+    "groupshared",+    "highp",+    "impl",+    "implements",+    "import",+    "inline",+    "instanceof",+    "interface",+    "layout",+    "lowp",+    "macro",+    "macro_rules",+    "match",+    "mediump",+    "meta",+    "mod",+    "module",+    "move",+    "mut",+    "mutable",+    "namespace",+    "new",+    "nil",+    "noexcept",+    "noinline",+    "nointerpolation",+    "non_coherent",+    "noncoherent",+    "noperspective",+    "null",+    "nullptr",+    "of",+    "operator",+    "package",+    "packoffset",+    "partition",+    "pass",+    "patch",+    "pixelfragment",+    "precise",+    "precision",+    "premerge",+    "priv",+    "protected",+    "pub",+    "public",+    "readonly",+    "ref",+    "regardless",+    "register",+    "reinterpret_cast",+    "require",+    "resource",+    "restrict",+    "self",+    "set",+    "shared",+    "sizeof",+    "smooth",+    "snorm",+    "static",+    "static_assert",+    "static_cast",+    "std",+    "subroutine",+    "super",+    "target",+    "template",+    "this",+    "thread_local",+    "throw",+    "trait",+    "try",+    "type",+    "typedef",+    "typeid",+    "typename",+    "typeof",+    "union",+    "unless",+    "unorm",+    "unsafe",+    "unsized",+    "use",+    "using",+    "varying",+    "virtual",+    "volatile",+    "wgsl",+    "where",+    "with",+    "writeonly",+    "yield"+  ]++-- | A type as it occurs in WGSL source. See+-- https://www.w3.org/TR/WGSL/#type-specifiers.+data Type+  = Bool+  | I32+  | U32+  | F16+  | F32+  | Vec2 Type+  | Vec3 Type+  | Vec4 Type+  | -- | @matCxR@ has @C@ columns of @R@ rows.+    Mat2x2 Type+  | Mat2x3 Type+  | Mat2x4 Type+  | Mat3x2 Type+  | Mat3x3 Type+  | Mat3x4 Type+  | Mat4x2 Type+  | Mat4x3 Type+  | Mat4x4 Type+  | Atomic Type+  | -- | An array, which is runtime-sized when the element count is absent.+    Array Type (Maybe Expression)+  | Ptr Type AddressSpace (Maybe AccessMode)+  | -- | A structure or type alias, referred to by name. Its definition is not+    -- known here, so it has no layout.+    Named Ident+  | -- | See https://www.w3.org/TR/WGSL/#sampled-texture-type.+    TextureSampled TextureDim Type+  | -- | Always two-dimensional.  See+    -- https://www.w3.org/TR/WGSL/#multisampled-texture-type.+    TextureMultisampled Type+  | -- | An external sampled texture type.  See+    -- https://www.w3.org/TR/WGSL/#external-texture-type.+    TextureExternal+  | -- | A storage texture type.  See+    -- https://www.w3.org/TR/WGSL/#texture-storage.+    TextureStorage StorageTextureDim TexelFormat AccessMode+  | -- | A depth texture type.  See+    -- https://www.w3.org/TR/WGSL/#texture-depth.+    TextureDepth DepthTexture+  | -- | A sampler.  See https://www.w3.org/TR/WGSL/#sampler-type.+    Sampler+  | -- | A comparison sampler.+    SamplerComparison+  deriving (Eq, Ord, Show)++-- | The dimensionality and arrangement of a sampled texture.  See+-- https://www.w3.org/TR/WGSL/#sampled-texture-type.+data TextureDim+  = Tex1d+  | Tex2d+  | Tex2dArray+  | Tex3d+  | TexCube+  | TexCubeArray+  deriving (Eq, Ord, Show)++-- | The dimensionality and arrangement of a storage texture.  See+-- https://www.w3.org/TR/WGSL/#texture-storage.+data StorageTextureDim+  = StorageTex1d+  | StorageTex2d+  | StorageTex2dArray+  | StorageTex3d+  deriving (Eq, Ord, Show)++-- | A depth texture type.  See https://www.w3.org/TR/WGSL/#texture-depth.+data DepthTexture+  = Depth2d+  | Depth2dArray+  | DepthCube+  | DepthCubeArray+  | DepthMultisampled2d+  deriving (Eq, Ord, Show)++-- | A texel format, used to parameterise the storage texture types.  See+-- https://www.w3.org/TR/WGSL/#texel-formats.+data TexelFormat+  = Rgba8Unorm+  | Rgba8Snorm+  | Rgba8Uint+  | Rgba8Sint+  | Rgba16Unorm+  | Rgba16Snorm+  | Rgba16Uint+  | Rgba16Sint+  | Rgba16Float+  | Rg8Unorm+  | Rg8Snorm+  | Rg8Uint+  | Rg8Sint+  | Rg16Unorm+  | Rg16Snorm+  | Rg16Uint+  | Rg16Sint+  | Rg16Float+  | R32Uint+  | R32Sint+  | R32Float+  | Rg32Uint+  | Rg32Sint+  | Rg32Float+  | Rgba32Uint+  | Rgba32Sint+  | Rgba32Float+  | Bgra8Unorm+  | R8Unorm+  | R8Snorm+  | R8Uint+  | R8Sint+  | R16Unorm+  | R16Snorm+  | R16Uint+  | R16Sint+  | R16Float+  | Rgb10a2Unorm+  | Rgb10a2Uint+  | Rg11b10Ufloat+  deriving (Eq, Ord, Show)++-- | @roundUp(k, n)@, which is @ceiling(n / k) * k@, for positive integers.+-- See https://www.w3.org/TR/WGSL/#roundup.+roundUp :: Int -> Int -> Int+roundUp k n = ((n + k - 1) `div` k) * k++-- | @roundDown(k, n)@, which is @floor(n / k) * k@, for positive integers.+-- See https://www.w3.org/TR/WGSL/#rounddown.+roundDown :: Int -> Int -> Int+roundDown k n = (n `div` k) * k++-- | @AlignOf@ of a type.  'Nothing' for pointers, texture and sampler types,+-- and for a 'Named' type, which is not resolved here.  See+-- https://www.w3.org/TR/WGSL/#alignment-and-size.+alignOf :: Type -> Maybe Int+alignOf Bool = Just 4+alignOf I32 = Just 4+alignOf U32 = Just 4+alignOf F16 = Just 2+alignOf F32 = Just 4+alignOf (Vec2 t) = (2 *) <$> alignOf t+alignOf (Vec3 t) = (4 *) <$> alignOf t+alignOf (Vec4 t) = (4 *) <$> alignOf t+-- The alignment of a matrix is that of its columns.+alignOf (Mat2x2 t) = alignOf $ Vec2 t+alignOf (Mat3x2 t) = alignOf $ Vec2 t+alignOf (Mat4x2 t) = alignOf $ Vec2 t+alignOf (Mat2x3 t) = alignOf $ Vec3 t+alignOf (Mat3x3 t) = alignOf $ Vec3 t+alignOf (Mat4x3 t) = alignOf $ Vec3 t+alignOf (Mat2x4 t) = alignOf $ Vec4 t+alignOf (Mat3x4 t) = alignOf $ Vec4 t+alignOf (Mat4x4 t) = alignOf $ Vec4 t+alignOf (Atomic _) = Just 4+alignOf (Array t _) = alignOf t+alignOf Ptr {} = Nothing+alignOf (Named _) = Nothing+alignOf TextureSampled {} = Nothing+alignOf (TextureMultisampled _) = Nothing+alignOf TextureExternal = Nothing+alignOf TextureStorage {} = Nothing+alignOf (TextureDepth _) = Nothing+alignOf Sampler = Nothing+alignOf SamplerComparison = Nothing++-- | @SizeOf@ of a type.  'Nothing' as for 'alignOf', and also for an array+-- whose element count is not a literal.  See+-- https://www.w3.org/TR/WGSL/#alignment-and-size.+sizeOf :: Type -> Maybe Int+sizeOf Bool = Just 4+sizeOf I32 = Just 4+sizeOf U32 = Just 4+sizeOf F16 = Just 2+sizeOf F32 = Just 4+sizeOf (Vec2 t) = (2 *) <$> sizeOf t+sizeOf (Vec3 t) = (3 *) <$> sizeOf t+sizeOf (Vec4 t) = (4 *) <$> sizeOf t+-- A matrix is laid out as an array of its columns.+sizeOf (Mat2x2 t) = (2 *) <$> strideOf (Vec2 t)+sizeOf (Mat3x2 t) = (3 *) <$> strideOf (Vec2 t)+sizeOf (Mat4x2 t) = (4 *) <$> strideOf (Vec2 t)+sizeOf (Mat2x3 t) = (2 *) <$> strideOf (Vec3 t)+sizeOf (Mat3x3 t) = (3 *) <$> strideOf (Vec3 t)+sizeOf (Mat4x3 t) = (4 *) <$> strideOf (Vec3 t)+sizeOf (Mat2x4 t) = (2 *) <$> strideOf (Vec4 t)+sizeOf (Mat3x4 t) = (3 *) <$> strideOf (Vec4 t)+sizeOf (Mat4x4 t) = (4 *) <$> strideOf (Vec4 t)+sizeOf (Atomic _) = Just 4+sizeOf (Array t (Just (ExpInt n _)))+  | n >= 0 = (n *) <$> strideOf t+sizeOf Array {} = Nothing+sizeOf Ptr {} = Nothing+sizeOf (Named _) = Nothing+sizeOf TextureSampled {} = Nothing+sizeOf (TextureMultisampled _) = Nothing+sizeOf TextureExternal = Nothing+sizeOf TextureStorage {} = Nothing+sizeOf (TextureDepth _) = Nothing+sizeOf Sampler = Nothing+sizeOf SamplerComparison = Nothing++-- | @StrideOf@ of an array with the given element type.  See+-- https://www.w3.org/TR/WGSL/#internal-value-layout.+strideOf :: Type -> Maybe Int+strideOf t = roundUp <$> alignOf t <*> sizeOf t++-- | Whether a type is host-shareable.  A 'Named' type is not resolved here,+-- and so is reported as not host-shareable.  See+-- https://www.w3.org/TR/WGSL/#host-shareable-types.+hostShareable :: Type -> Bool+-- bool is not a numeric scalar type, and a vector of bool is not a numeric+-- vector type.+hostShareable Bool = False+hostShareable I32 = True+hostShareable U32 = True+hostShareable F16 = True+hostShareable F32 = True+hostShareable (Vec2 t) = hostShareable t+hostShareable (Vec3 t) = hostShareable t+hostShareable (Vec4 t) = hostShareable t+hostShareable (Mat2x2 t) = hostShareable t+hostShareable (Mat2x3 t) = hostShareable t+hostShareable (Mat2x4 t) = hostShareable t+hostShareable (Mat3x2 t) = hostShareable t+hostShareable (Mat3x3 t) = hostShareable t+hostShareable (Mat3x4 t) = hostShareable t+hostShareable (Mat4x2 t) = hostShareable t+hostShareable (Mat4x3 t) = hostShareable t+hostShareable (Mat4x4 t) = hostShareable t+hostShareable (Atomic _) = True+-- A fixed-size array is host-shareable only if it has a creation-fixed+-- footprint, so its element count must be a literal.  A runtime-sized array+-- has no count at all.+hostShareable (Array t Nothing) = hostShareable t+hostShareable (Array t (Just (ExpInt _ _))) = hostShareable t+hostShareable Array {} = False+hostShareable Ptr {} = False+hostShareable (Named _) = False+hostShareable TextureSampled {} = False+hostShareable (TextureMultisampled _) = False+hostShareable TextureExternal = False+hostShareable TextureStorage {} = False+hostShareable (TextureDepth _) = False+hostShareable Sampler = False+hostShareable SamplerComparison = False++-- | @AlignOfMember@ of a structure member.  'Nothing' if its @\@align@+-- argument is not a literal.  See+-- https://www.w3.org/TR/WGSL/#structure-member-layout.+alignOfMember :: StructMember -> Maybe Int+alignOfMember (StructMember attribs _ t) =+  case [args | Attribute name args <- attribs, name == "align"] of+    [] -> alignOf t+    [[ExpInt k _]] -> Just k+    _ -> Nothing++-- | @SizeOfMember@ of a structure member.  'Nothing' if its @\@size@+-- argument is not a literal.  See+-- https://www.w3.org/TR/WGSL/#structure-member-layout.+sizeOfMember :: StructMember -> Maybe Int+sizeOfMember (StructMember attribs _ t) =+  case [args | Attribute name args <- attribs, name == "size"] of+    [] -> sizeOf t+    [[ExpInt k _]] -> Just k+    _ -> Nothing++-- | @AlignOf@ of a structure with the given members.  'Nothing' if there are+-- no members.  See https://www.w3.org/TR/WGSL/#structure-member-layout.+alignOfStruct :: [StructMember] -> Maybe Int+alignOfStruct [] = Nothing+alignOfStruct members = maximum <$> mapM alignOfMember members++-- | @OffsetOfMember@ of every member of a structure, which is known even when+-- the structure has no @SizeOf@.  See+-- https://www.w3.org/TR/WGSL/#structure-member-layout.+offsetOfMembers :: [StructMember] -> Maybe [Int]+offsetOfMembers [] = Nothing+offsetOfMembers members = do+  aligns <- mapM alignOfMember $ drop 1 members+  sizes <- mapM sizeOfMember $ init members+  pure $+    scanl+      (\prev_off (al, prev_sz) -> roundUp al (prev_off + prev_sz))+      0+      (zip aligns sizes)++-- | @SizeOf@ of a structure with the given members.  'Nothing' if any member+-- has no @SizeOfMember@.  See+-- https://www.w3.org/TR/WGSL/#structure-member-layout.+sizeOfStruct :: [StructMember] -> Maybe Int+sizeOfStruct members = do+  struct_align <- alignOfStruct members+  offsets <- offsetOfMembers members+  last_size <- sizeOfMember $ last members+  pure $ roundUp struct_align (last offsets + last_size)++-- | A binary operator. See https://www.w3.org/TR/WGSL/#expressions.+data BinaryOperator+  = Add+  | Subtract+  | Multiply+  | Divide+  | Remainder+  | BitwiseAnd+  | BitwiseOr+  | BitwiseExclusiveOr+  | ShiftLeft+  | ShiftRight+  | Less+  | LessEqual+  | Greater+  | GreaterEqual+  | Equal+  | NotEqual+  | ShortCircuitAnd+  | ShortCircuitOr+  deriving (Eq, Ord, Show)++-- | A unary operator.  See https://www.w3.org/TR/WGSL/#expressions.+data UnaryOperator+  = Negate+  | LogicalNegate+  | BitwiseComplement+  | Indirection+  | AddressOf+  deriving (Eq, Ord, Show)++-- | The type denoted by an integer literal, which is determined by its+-- suffix.  See https://www.w3.org/TR/WGSL/#numeric-literals.+data IntLiteralType+  = -- | No suffix.+    AbstractInt+  | -- | The @i@ suffix.+    LitI32+  | -- | The @u@ suffix.+    LitU32+  deriving (Eq, Ord, Show)++-- | The type denoted by a floating point literal, which is determined by its+-- suffix.  See https://www.w3.org/TR/WGSL/#numeric-literals.+data FloatLiteralType+  = -- | No suffix.+    AbstractFloat+  | -- | The @f@ suffix.+    LitF32+  | -- | The @h@ suffix.  Using it requires the @enable f16;@ directive.+    LitF16+  deriving (Eq, Ord, Show)++-- | An expression.  See https://www.w3.org/TR/WGSL/#expressions.+data Expression+  = ExpBool Bool+  | -- | An integer literal.  Prefer 'intExp', which checks that the value can+    -- be denoted by a literal of the given type.+    ExpInt Int IntLiteralType+  | -- | A floating point literal.  Prefer 'floatExp'.+    ExpFloat Double FloatLiteralType+  | ExpIdent Ident+  | ExpBinary BinaryOperator Expression Expression+  | ExpUnary UnaryOperator Expression+  | -- | A call.  The first list is the callee's template arguments, as in+    -- @bitcast<i32>(x)@.+    ExpCall Ident [Expression] [Expression]+  | -- | A type in expression position, as a template argument is.+    ExpType Type+  | ExpIndex Expression Expression+  | ExpMember Expression Ident+  deriving (Eq, Ord, Show)++-- | A statement.  See https://www.w3.org/TR/WGSL/#statements.+data Statement+  = -- | The empty statement.+    Empty+  | -- | A line comment, which is not a statement in WGSL, but is useful in+    -- generated code.  May contain linebreaks.+    Comment T.Text+  | Compound [Statement]+  | -- | @let x = e@, with an optional type.+    Let Ident (Maybe Type) Expression+  | -- | @const x = e@, with an optional type.+    Const Ident (Maybe Type) Expression+  | -- | @var x : T = e@.+    Var Ident (Maybe Type) (Maybe Expression)+  | Assign Expression AssignmentOperator Expression+  | -- | @_ = e@.+    PhonyAssign Expression+  | Increment Expression+  | Decrement Expression+  | -- | @const_assert e@.  'GlobalAssert' is its module-scope counterpart.+    ConstAssert Expression+  | -- | An @if@, its @else if@ clauses, and its optional @else@ clause.+    If Expression [Statement] [(Expression, [Statement])] (Maybe [Statement])+  | Switch Expression (NonEmpty SwitchClause)+  | -- | A @loop@, with its optional @continuing@ block.+    Loop [Statement] (Maybe Continuing)+  | For Ident Expression Expression ForUpdate [Statement]+  | While Expression [Statement]+  | Break+  | Continue+  | Discard+  | Call Ident [Expression] [Expression]+  | Return (Maybe Expression)+  deriving (Eq, Ord, Show)++-- | A clause of a @switch@ statement.  See+-- https://www.w3.org/TR/WGSL/#switch-statement.+data SwitchClause+  = -- | A @case@ with one or more selectors.+    Case (NonEmpty CaseSelector) [Statement]+  | -- | A @default@ clause on its own.+    DefaultCase [Statement]+  deriving (Eq, Ord, Show)++-- | A selector of a @case@ clause.  See+-- https://www.w3.org/TR/WGSL/#switch-statement.+data CaseSelector+  = CaseExp Expression+  | CaseDefault+  deriving (Eq, Ord, Show)++-- | The @continuing@ block of a @loop@, which may end in a @break if@.  See+-- https://www.w3.org/TR/WGSL/#continuing-statement.+data Continuing = Continuing [Statement] (Maybe Expression)+  deriving (Eq, Ord, Show)++-- | The operator of an assignment: plain assignment, or one of the compound+-- assignment operators.  See https://www.w3.org/TR/WGSL/#assignment.+data AssignmentOperator+  = AssignSimple+  | AssignAdd+  | AssignSubtract+  | AssignMultiply+  | AssignDivide+  | AssignRemainder+  | AssignBitwiseAnd+  | AssignBitwiseOr+  | AssignBitwiseExclusiveOr+  | AssignShiftLeft+  | AssignShiftRight+  deriving (Eq, Ord, Show)++-- | The update of a @for@ loop, which the grammar restricts to the statements+-- that update a variable, or to a function call. These mirror the+-- correspondingly named statements. See+-- https://www.w3.org/TR/WGSL/#for-statement.+data ForUpdate+  = -- | The left-hand side must be an assignable expression.+    UpdateAssign Expression AssignmentOperator Expression+  | UpdatePhonyAssign Expression+  | UpdateIncrement Expression+  | UpdateDecrement Expression+  | UpdateCall Ident [Expression] [Expression]+  deriving (Eq, Ord, Show)++-- | An attribute.  See https://www.w3.org/TR/WGSL/#attributes.+data Attribute = Attribute Ident [Expression]+  deriving (Eq, Ord, Show)++-- | A formal parameter of a function.  See+-- https://www.w3.org/TR/WGSL/#function-declaration-sec.+data Param = Param [Attribute] Ident Type+  deriving (Eq, Ord, Show)++-- | A function declaration.  See+-- https://www.w3.org/TR/WGSL/#function-declaration-sec.+data FunctionDecl = FunctionDecl+  { functionName :: Ident,+    functionAttributes :: [Attribute],+    functionParams :: [Param],+    functionReturn :: Maybe ([Attribute], Type),+    functionBody :: [Statement]+  }+  deriving (Eq, Ord, Show)++-- | A structure member.  See https://www.w3.org/TR/WGSL/#struct-types.+data StructMember = StructMember [Attribute] Ident Type+  deriving (Eq, Ord, Show)++-- | A structure declaration.  See+-- https://www.w3.org/TR/WGSL/#struct-types.+data StructDecl = StructDecl Ident [StructMember]+  deriving (Eq, Ord, Show)++-- | The access modes supported by a memory view.  See+-- https://www.w3.org/TR/WGSL/#memory-access-mode.+data AccessMode = Read | Write | ReadWrite+  deriving (Eq, Ord, Show)++-- | An address space.  The @handle@ space is never written in WGSL source, so+-- it is not represented.  See https://www.w3.org/TR/WGSL/#address-spaces.+data AddressSpace+  = Function+  | Private+  | Workgroup+  | Uniform+  | Storage+  | Immediate+  deriving (Eq, Ord, Show)++-- | A module-scope declaration.  See+-- https://www.w3.org/TR/WGSL/#module-scope-declarations.+data GlobalDecl+  = GlobalFunction FunctionDecl+  | GlobalStruct StructDecl+  | -- | The access mode may only be given along with the address space, and+    -- defaults to the one of the address space when omitted.+    GlobalVariable [Attribute] AddressSpace (Maybe AccessMode) Ident (Maybe Type) (Maybe Expression)+  | GlobalOverride [Attribute] Ident (Maybe Type) (Maybe Expression)+  | -- | A module-scope @const@, which takes no attributes.+    GlobalConst Ident (Maybe Type) Expression+  | -- | A type alias.+    GlobalAlias Ident Type+  | -- | A module-scope @const_assert@.+    GlobalAssert Expression+  deriving (Eq, Ord, Show)++-- | A WGSL module: its directives, then its declarations.  See+-- https://www.w3.org/TR/WGSL/#wgsl-module-definition.+data Module = Module [Directive] [GlobalDecl]+  deriving (Eq, Ord, Show)++-- | A directive.  See https://www.w3.org/TR/WGSL/#directives.+data Directive+  = Enable (NonEmpty EnableExtension)+  | Requires (NonEmpty LanguageExtension)+  | -- | A diagnostic filter.  Rule names are not a closed set, so the rule is+    -- given as an identifier.+    Diagnostic Severity Ident+  deriving (Eq, Ord, Show)++-- | The severity of a diagnostic.  See+-- https://www.w3.org/TR/WGSL/#diagnostics.+data Severity = Error | Warning | Info | Off+  deriving (Eq, Ord, Show)++-- | An extension that an @enable@ directive may enable.  See+-- https://www.w3.org/TR/WGSL/#enable-extensions.+data EnableExtension+  = EnableF16+  | EnableClipDistances+  | EnableDualSourceBlending+  | EnableSubgroups+  | EnablePrimitiveIndex+  | EnableSubgroupSizeControl+  deriving (Eq, Ord, Show)++-- | A language extension that a @requires@ directive may require.  See+-- https://www.w3.org/TR/WGSL/#language-extensions.+data LanguageExtension+  = ReadonlyAndReadwriteStorageTextures+  | Packed4x8IntegerDotProduct+  | UnrestrictedPointerParameters+  | PointerCompositeAccess+  | UniformBufferStandardLayout+  | SubgroupId+  | SubgroupUniformity+  | TextureAndSamplerLet+  | TextureFormatsTier1+  | LinearIndexing+  | ImmediateAddressSpace+  | FragmentDepth+  | BufferView+  | SwizzleAssignment+  deriving (Eq, Ord, Show)++-- | The @\@group@ and @\@binding@ attributes for the given group and+-- binding.+bindingAttribs :: Int -> Int -> [Attribute]+bindingAttribs grp binding =+  [ Attribute "group" [ExpInt grp AbstractInt],+    Attribute "binding" [ExpInt binding AbstractInt]+  ]++-- | Construct an integer literal, or 'Nothing' if the value cannot be denoted+-- by a literal of the given type.+intExp :: Int -> IntLiteralType -> Maybe Expression+intExp v t+  | representable = Just $ ExpInt v t+  | otherwise = Nothing+  where+    representable = case t of+      AbstractInt -> magnitude <= 2 ^ (63 :: Int) - 1+      LitI32 -> magnitude <= 2 ^ (31 :: Int) - 1+      LitU32 -> toInteger v >= 0 && toInteger v <= 2 ^ (32 :: Int) - 1+    magnitude = abs $ toInteger v++-- | Construct a floating point literal, or 'Nothing' if the value cannot be+-- denoted by a literal of the given type.+floatExp :: Double -> FloatLiteralType -> Maybe Expression+floatExp v t+  | isNaN v || isInfinite v = Nothing+  | maybe False (abs v >) largestFinite = Nothing+  | otherwise = Just $ ExpFloat v t+  where+    largestFinite = case t of+      -- Any finite Double denotes an AbstractFloat.+      AbstractFloat -> Nothing+      LitF32 -> Just 3.40282346638528859812e38+      LitF16 -> Just 65504++-- | Reinterpret the bits of an expression as another type.  See+-- https://www.w3.org/TR/WGSL/#bitcast-builtin.+bitcast :: Type -> Expression -> Expression+bitcast t e = ExpCall "bitcast" [ExpType t] [e]++-- | A template argument list, which is omitted when empty.+templateArgs :: [Doc a] -> Doc a+templateArgs [] = mempty+templateArgs args = "<" <> commasep args <> ">"++templateList :: [Expression] -> Doc a+templateList = templateArgs . map pretty++-- | Separate with commas.+commasep :: [Doc a] -> Doc a+commasep = hsep . punctuate comma++-- | Like commasep, but a newline after every comma.+commastack :: [Doc a] -> Doc a+commastack = align . vsep . punctuate comma++-- | Separate with semicolons and newlines.+-- | Separate with linebreaks.+stack :: [Doc a] -> Doc a+stack = align . mconcat . punctuate line++(</>) :: Doc a -> Doc a -> Doc a+a </> b = a <> line <> b++instance Pretty Type where+  pretty Bool = "bool"+  pretty I32 = "i32"+  pretty U32 = "u32"+  pretty F16 = "f16"+  pretty F32 = "f32"+  pretty (Vec2 t) = generator "vec2" [pretty t]+  pretty (Vec3 t) = generator "vec3" [pretty t]+  pretty (Vec4 t) = generator "vec4" [pretty t]+  pretty (Mat2x2 t) = generator "mat2x2" [pretty t]+  pretty (Mat2x3 t) = generator "mat2x3" [pretty t]+  pretty (Mat2x4 t) = generator "mat2x4" [pretty t]+  pretty (Mat3x2 t) = generator "mat3x2" [pretty t]+  pretty (Mat3x3 t) = generator "mat3x3" [pretty t]+  pretty (Mat3x4 t) = generator "mat3x4" [pretty t]+  pretty (Mat4x2 t) = generator "mat4x2" [pretty t]+  pretty (Mat4x3 t) = generator "mat4x3" [pretty t]+  pretty (Mat4x4 t) = generator "mat4x4" [pretty t]+  pretty (Atomic t) = generator "atomic" [pretty t]+  pretty (Array t Nothing) = generator "array" [pretty t]+  pretty (Array t (Just n)) = generator "array" [pretty t, pretty n]+  pretty (Ptr t as am) =+    generator "ptr" $ [pretty as, pretty t] ++ maybe [] (pure . pretty) am+  pretty (Named t) = pretty t+  pretty (TextureSampled dim t) = generator (textureDim dim) [pretty t]+  pretty (TextureMultisampled t) =+    generator "texture_multisampled_2d" [pretty t]+  pretty TextureExternal = "texture_external"+  pretty (TextureStorage dim fmt am) =+    generator (storageTextureDim dim) [pretty fmt, pretty am]+  pretty (TextureDepth d) = pretty d+  pretty Sampler = "sampler"+  pretty SamplerComparison = "sampler_comparison"++-- | The name of the sampled texture type-generator for a dimensionality.+textureDim :: TextureDim -> Ident+textureDim Tex1d = "texture_1d"+textureDim Tex2d = "texture_2d"+textureDim Tex2dArray = "texture_2d_array"+textureDim Tex3d = "texture_3d"+textureDim TexCube = "texture_cube"+textureDim TexCubeArray = "texture_cube_array"++-- | The name of the storage texture type-generator for a dimensionality.+storageTextureDim :: StorageTextureDim -> Ident+storageTextureDim StorageTex1d = "texture_storage_1d"+storageTextureDim StorageTex2d = "texture_storage_2d"+storageTextureDim StorageTex2dArray = "texture_storage_2d_array"+storageTextureDim StorageTex3d = "texture_storage_3d"++instance Pretty DepthTexture where+  pretty Depth2d = "texture_depth_2d"+  pretty Depth2dArray = "texture_depth_2d_array"+  pretty DepthCube = "texture_depth_cube"+  pretty DepthCubeArray = "texture_depth_cube_array"+  pretty DepthMultisampled2d = "texture_depth_multisampled_2d"++instance Pretty TexelFormat where+  pretty Rgba8Unorm = "rgba8unorm"+  pretty Rgba8Snorm = "rgba8snorm"+  pretty Rgba8Uint = "rgba8uint"+  pretty Rgba8Sint = "rgba8sint"+  pretty Rgba16Unorm = "rgba16unorm"+  pretty Rgba16Snorm = "rgba16snorm"+  pretty Rgba16Uint = "rgba16uint"+  pretty Rgba16Sint = "rgba16sint"+  pretty Rgba16Float = "rgba16float"+  pretty Rg8Unorm = "rg8unorm"+  pretty Rg8Snorm = "rg8snorm"+  pretty Rg8Uint = "rg8uint"+  pretty Rg8Sint = "rg8sint"+  pretty Rg16Unorm = "rg16unorm"+  pretty Rg16Snorm = "rg16snorm"+  pretty Rg16Uint = "rg16uint"+  pretty Rg16Sint = "rg16sint"+  pretty Rg16Float = "rg16float"+  pretty R32Uint = "r32uint"+  pretty R32Sint = "r32sint"+  pretty R32Float = "r32float"+  pretty Rg32Uint = "rg32uint"+  pretty Rg32Sint = "rg32sint"+  pretty Rg32Float = "rg32float"+  pretty Rgba32Uint = "rgba32uint"+  pretty Rgba32Sint = "rgba32sint"+  pretty Rgba32Float = "rgba32float"+  pretty Bgra8Unorm = "bgra8unorm"+  pretty R8Unorm = "r8unorm"+  pretty R8Snorm = "r8snorm"+  pretty R8Uint = "r8uint"+  pretty R8Sint = "r8sint"+  pretty R16Unorm = "r16unorm"+  pretty R16Snorm = "r16snorm"+  pretty R16Uint = "r16uint"+  pretty R16Sint = "r16sint"+  pretty R16Float = "r16float"+  pretty Rgb10a2Unorm = "rgb10a2unorm"+  pretty Rgb10a2Uint = "rgb10a2uint"+  pretty Rg11b10Ufloat = "rg11b10ufloat"++-- | A predeclared type-generator applied to its template arguments.+generator :: Ident -> [Doc a] -> Doc a+generator name args = pretty name <> templateArgs args++intSuffix :: IntLiteralType -> Doc a+intSuffix AbstractInt = mempty+intSuffix LitI32 = "i"+intSuffix LitU32 = "u"++floatSuffix :: FloatLiteralType -> Doc a+floatSuffix AbstractFloat = mempty+floatSuffix LitF32 = "f"+floatSuffix LitF16 = "h"++instance Pretty BinaryOperator where+  pretty Add = "+"+  pretty Subtract = "-"+  pretty Multiply = "*"+  pretty Divide = "/"+  pretty Remainder = "%"+  pretty BitwiseAnd = "&"+  pretty BitwiseOr = "|"+  pretty BitwiseExclusiveOr = "^"+  pretty ShiftLeft = "<<"+  pretty ShiftRight = ">>"+  pretty Less = "<"+  pretty LessEqual = "<="+  pretty Greater = ">"+  pretty GreaterEqual = ">="+  pretty Equal = "=="+  pretty NotEqual = "!="+  pretty ShortCircuitAnd = "&&"+  pretty ShortCircuitOr = "||"++instance Pretty UnaryOperator where+  pretty Negate = "-"+  pretty LogicalNegate = "!"+  pretty BitwiseComplement = "~"+  pretty Indirection = "*"+  pretty AddressOf = "&"++instance Pretty Expression where+  pretty (ExpBool True) = "true"+  pretty (ExpBool False) = "false"+  pretty (ExpInt x t) = pretty x <> intSuffix t+  pretty (ExpFloat x t) = pretty x <> floatSuffix t+  pretty (ExpIdent x) = pretty x+  pretty (ExpUnary op e) = parens (pretty op <> pretty e)+  pretty (ExpBinary op e1 e2) = parens (pretty e1 <+> pretty op <+> pretty e2)+  pretty (ExpCall f targs args) =+    pretty f <> templateList targs <> parens (commasep $ map pretty args)+  pretty (ExpType t) = pretty t+  pretty (ExpIndex e i) = pretty e <> brackets (pretty i)+  pretty (ExpMember x y) = pretty x <> "." <> pretty y++instance Pretty Statement where+  pretty Empty = ";"+  pretty (Comment c) = vsep (map ("//" <+>) (pretty <$> T.lines c))+  pretty (Compound body) = block body+  pretty (Let x t e) =+    "let" <+> optionallyTyped x t <+> "=" <+> pretty e <> ";"+  pretty (Const x t e) =+    "const" <+> optionallyTyped x t <+> "=" <+> pretty e <> ";"+  pretty (Var x t e) =+    "var" <+> optionallyTyped x t <> prettyInit e <> ";"+  pretty (Assign lhs op e) = pretty lhs <+> pretty op <+> pretty e <> ";"+  pretty (PhonyAssign e) = "_" <+> "=" <+> pretty e <> ";"+  pretty (Increment e) = pretty e <> "++;"+  pretty (Decrement e) = pretty e <> "--;"+  pretty (ConstAssert e) = "const_assert" <+> pretty e <> ";"+  pretty (If cond th elifs el) =+    hsep $+      ["if" <+> pretty cond <+> block th]+        ++ map elseIf elifs+        ++ maybe [] (\body -> ["else" <+> block body]) el+    where+      elseIf (c, body) = "else if" <+> pretty c <+> block body+  pretty (Switch e clauses) =+    "switch" <+> pretty e <+> braceBlock (map pretty (NE.toList clauses))+  pretty (Loop body continuing) =+    "loop"+      <+> braceBlock (map pretty body ++ maybe [] (pure . pretty) continuing)+  pretty (For x initializer cond upd body) =+    "for"+      <+> parens+        ( "var"+            <+> pretty x+            <+> "="+            <+> pretty initializer+            <> ";"+            <+> pretty cond+            <> ";"+            <+> pretty upd+        )+      <+> block body+  pretty (While cond body) = "while" <+> pretty cond <+> block body+  pretty (Call f targs args) =+    pretty f <> templateList targs <> parens (commasep $ map pretty args) <> ";"+  pretty (Return Nothing) = "return;"+  pretty (Return (Just e)) = "return" <+> pretty e <> ";"+  pretty Break = "break;"+  pretty Continue = "continue;"+  pretty Discard = "discard;"++instance Pretty SwitchClause where+  pretty (Case selectors body) =+    "case"+      <+> commasep (map pretty (NE.toList selectors))+      <> ":"+      <+> block body+  pretty (DefaultCase body) = "default:" <+> block body++instance Pretty CaseSelector where+  pretty (CaseExp e) = pretty e+  pretty CaseDefault = "default"++instance Pretty Continuing where+  pretty (Continuing body breakIf) =+    "continuing"+      <+> braceBlock+        ( map pretty body+            ++ maybe [] (\e -> ["break if" <+> pretty e <> ";"]) breakIf+        )++-- | A brace-enclosed statement sequence, as every block in WGSL is.  See+-- https://www.w3.org/TR/WGSL/#compound-statement-section.+block :: [Statement] -> Doc a+block = braceBlock . map pretty++-- | An @optionally_typed_ident@.+optionallyTyped :: Ident -> Maybe Type -> Doc a+optionallyTyped x t = pretty x <> maybe mempty ((" :" <+>) . pretty) t++-- | An optional initialiser.+prettyInit :: Maybe Expression -> Doc a+prettyInit = maybe mempty ((" =" <+>) . pretty)++instance Pretty AssignmentOperator where+  pretty AssignSimple = "="+  pretty AssignAdd = "+="+  pretty AssignSubtract = "-="+  pretty AssignMultiply = "*="+  pretty AssignDivide = "/="+  pretty AssignRemainder = "%="+  pretty AssignBitwiseAnd = "&="+  pretty AssignBitwiseOr = "|="+  pretty AssignBitwiseExclusiveOr = "^="+  pretty AssignShiftLeft = "<<="+  pretty AssignShiftRight = ">>="++-- | A brace-enclosed, indented sequence.+braceBlock :: [Doc a] -> Doc a+braceBlock [] = "{ }"+braceBlock docs = "{" </> indent 2 (stack docs) </> "}"++instance Pretty ForUpdate where+  pretty (UpdateAssign lhs op e) = pretty lhs <+> pretty op <+> pretty e+  pretty (UpdatePhonyAssign e) = "_" <+> "=" <+> pretty e+  pretty (UpdateIncrement e) = pretty e <> "++"+  pretty (UpdateDecrement e) = pretty e <> "--"+  pretty (UpdateCall f targs args) =+    pretty f <> templateList targs <> parens (commasep $ map pretty args)++instance Pretty Attribute where+  pretty (Attribute name []) = "@" <> pretty name+  pretty (Attribute name args) =+    "@" <> pretty name <> parens (commasep $ map pretty args)++instance Pretty Param where+  pretty (Param attribs name typ) =+    hsep $ map pretty attribs ++ [pretty name <+> ":" <+> pretty typ]++prettyParams :: [Param] -> Doc a+prettyParams [] = "()"+prettyParams params = "(" </> indent 2 (commastack (map pretty params)) </> ")"++instance Pretty FunctionDecl where+  pretty (FunctionDecl name attribs params ret body) =+    prettyAttribs attribs <> header <+> block body+    where+      header = case ret of+        Nothing -> "fn" <+> pretty name <> prettyParams params+        Just (ret_attribs, t) ->+          "fn"+            <+> pretty name+            <> prettyParams params+            <+> "->"+            <+> hsep (map pretty ret_attribs ++ [pretty t])++-- | Attributes, followed by a line break if there are any at all.+prettyAttribs :: [Attribute] -> Doc a+prettyAttribs [] = mempty+prettyAttribs attribs = hsep (map pretty attribs) <> line++instance Pretty StructMember where+  pretty (StructMember attribs name typ) =+    hsep $ map pretty attribs ++ [pretty name <+> ":" <+> pretty typ]++instance Pretty StructDecl where+  pretty (StructDecl name fields) =+    "struct"+      <+> pretty name+      <+> "{"+        </> indent 2 (commastack (map pretty fields))+        </> "}"++instance Pretty AccessMode where+  pretty Read = "read"+  pretty Write = "write"+  pretty ReadWrite = "read_write"++instance Pretty AddressSpace where+  pretty Storage = "storage"+  pretty Private = "private"+  pretty Immediate = "immediate"+  pretty Uniform = "uniform"+  pretty Workgroup = "workgroup"+  pretty Function = "function"++instance Pretty Severity where+  pretty Error = "error"+  pretty Warning = "warning"+  pretty Info = "info"+  pretty Off = "off"++instance Pretty EnableExtension where+  pretty EnableF16 = "f16"+  pretty EnableClipDistances = "clip_distances"+  pretty EnableDualSourceBlending = "dual_source_blending"+  pretty EnableSubgroups = "subgroups"+  pretty EnablePrimitiveIndex = "primitive_index"+  pretty EnableSubgroupSizeControl = "subgroup_size_control"++instance Pretty LanguageExtension where+  pretty ReadonlyAndReadwriteStorageTextures =+    "readonly_and_readwrite_storage_textures"+  pretty Packed4x8IntegerDotProduct = "packed_4x8_integer_dot_product"+  pretty UnrestrictedPointerParameters = "unrestricted_pointer_parameters"+  pretty PointerCompositeAccess = "pointer_composite_access"+  pretty UniformBufferStandardLayout = "uniform_buffer_standard_layout"+  pretty SubgroupId = "subgroup_id"+  pretty SubgroupUniformity = "subgroup_uniformity"+  pretty TextureAndSamplerLet = "texture_and_sampler_let"+  pretty TextureFormatsTier1 = "texture_formats_tier1"+  pretty LinearIndexing = "linear_indexing"+  pretty ImmediateAddressSpace = "immediate_address_space"+  pretty FragmentDepth = "fragment_depth"+  pretty BufferView = "buffer_view"+  pretty SwizzleAssignment = "swizzle_assignment"++instance Pretty Directive where+  pretty (Enable exts) =+    "enable" <+> commasep (map pretty (NE.toList exts)) <> ";"+  pretty (Requires exts) =+    "requires" <+> commasep (map pretty (NE.toList exts)) <> ";"+  pretty (Diagnostic severity rule) =+    "diagnostic"+      <> parens (pretty severity <> "," <+> pretty rule)+      <> ";"++instance Pretty Module where+  pretty (Module directives decls) =+    stack $ map pretty directives ++ map pretty decls++instance Pretty GlobalDecl where+  pretty (GlobalFunction fun) = pretty fun+  pretty (GlobalStruct struct) = pretty struct+  pretty (GlobalVariable attribs as am name typ e) =+    prettyAttribs attribs+      <> "var<"+      <> pretty as+      <> maybe "" ((", " <>) . pretty) am+      <> ">"+      <+> optionallyTyped name typ+      <> prettyInit e+      <> ";"+  pretty (GlobalOverride attribs name typ e) =+    prettyAttribs attribs+      <> "override"+      <+> optionallyTyped name typ+      <> prettyInit e+      <> ";"+  pretty (GlobalConst name typ e) =+    "const" <+> optionallyTyped name typ <+> "=" <+> pretty e <> ";"+  pretty (GlobalAlias name t) =+    "alias" <+> pretty name <+> "=" <+> pretty t <> ";"+  pretty (GlobalAssert e) = "const_assert" <+> pretty e <> ";"
+ test/Test.hs view
@@ -0,0 +1,1078 @@+module Main (main) where++import Data.List (nub)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Text qualified as T+import Language.WGSL+import Prettyprinter (Pretty, defaultLayoutOptions, layoutPretty, pretty, vsep)+import Prettyprinter.Render.Text (renderStrict)+import Test.Tasty+import Test.Tasty.HUnit++-- | The offsets, alignment and size of a structure, which the tests check+-- together.+structLayout :: [Type] -> (Maybe [Int], Maybe Int, Maybe Int)+structLayout = structLayoutOf . map (StructMember [] "m")++-- | As 'structLayout', but for members given in full, so that they may carry+-- layout attributes.+structLayoutOf :: [StructMember] -> (Maybe [Int], Maybe Int, Maybe Int)+structLayoutOf fs = (offsetOfMembers fs, alignOfStruct fs, sizeOfStruct fs)++-- | The alignment and size of a type, which the tests check together.+layout :: Type -> Maybe (Int, Int)+layout t = (,) <$> alignOf t <*> sizeOf t++-- | A convenient literal for tests that only need some expression.+one, two :: Expression+one = ExpInt 1 AbstractInt+two = ExpInt 2 AbstractInt++-- | Render a statement sequence, as a code generator would.+renderBody :: [Statement] -> T.Text+renderBody =+  renderStrict . layoutPretty defaultLayoutOptions . vsep . map pretty++-- | Render an AST fragment as a code generator would.+render :: (Pretty a) => a -> T.Text+render = renderStrict . layoutPretty defaultLayoutOptions . pretty++-- | A pointer type is spelled @ptr<AS,T,AM>@, where the access mode is+-- optional.  See https://www.w3.org/TR/WGSL/#ref-ptr-types.+pointerTests :: TestTree+pointerTests =+  testGroup+    "Ptr"+    [ testCase "without access mode" $+        render (Ptr I32 Function Nothing)+          @?= "ptr<function, i32>",+      testCase "with access mode" $+        render (Ptr I32 Function (Just ReadWrite))+          @?= "ptr<function, i32, read_write>",+      testCase "uniform" $+        render (Ptr F32 Uniform Nothing)+          @?= "ptr<uniform, f32>",+      testCase "vector store type" $+        render (Ptr (Vec3 F32) Workgroup (Just Read))+          @?= "ptr<workgroup, vec3<f32>, read>",+      testCase "storage" $+        render (Ptr I32 Storage (Just Read))+          @?= "ptr<storage, i32, read>"+    ]++-- | An access mode is a predeclared enumerant.  See+-- https://www.w3.org/TR/WGSL/#memory-access-mode.+accessModeTests :: TestTree+accessModeTests =+  testGroup+    "AccessMode"+    [ testCase "read" $ render Read @?= "read",+      testCase "write" $ render Write @?= "write",+      testCase "read_write" $ render ReadWrite @?= "read_write"+    ]++-- | The address space of a module-scope variable is given as a template list+-- after the @var@ keyword, with the access mode second if present.  See+-- https://www.w3.org/TR/WGSL/#var-decls.+globalVarTests :: TestTree+globalVarTests =+  testGroup+    "GlobalVariable"+    [ testCase "without access mode" $+        render (GlobalVariable [] Workgroup Nothing "x" (Just I32) Nothing)+          @?= "var<workgroup> x : i32;",+      testCase "with access mode" $+        render (GlobalVariable [] Storage (Just ReadWrite) "x" (Just (Array I32 Nothing)) Nothing)+          @?= "var<storage, read_write> x : array<i32>;",+      testCase "private" $+        render (GlobalVariable [] Private Nothing "x" (Just F32) Nothing)+          @?= "var<private> x : f32;",+      testCase "immediate" $+        render (GlobalVariable [] Immediate Nothing "x" (Just (Named "S")) Nothing)+          @?= "var<immediate> x : S;",+      testCase "with attributes" $+        render (GlobalVariable (bindingAttribs 0 1) Storage (Just Read) "x" (Just (Array F32 Nothing)) Nothing)+          @?= "@group(0) @binding(1)\nvar<storage, read> x : array<f32>;"+    ]++-- | A literal is never negative, and its suffix fixes its type.  See+-- https://www.w3.org/TR/WGSL/#numeric-literals.+intLitTests :: TestTree+intLitTests =+  testGroup+    "intExp"+    [ testCase "no suffix" $+        fmap render (intExp 5 AbstractInt) @?= Just "5",+      testCase "i suffix" $+        fmap render (intExp 5 LitI32) @?= Just "5i",+      testCase "u suffix" $+        fmap render (intExp 5 LitU32) @?= Just "5u",+      testCase "negated literal" $+        fmap render (intExp (-5) LitI32) @?= Just "-5i",+      testCase "largest i32" $+        fmap render (intExp 2147483647 LitI32) @?= Just "2147483647i",+      testCase "i32 overflow" $+        fmap render (intExp 2147483648 LitI32) @?= Nothing,+      -- The literal is 2147483648, which i32 cannot represent, even though+      -- the negated value can be.+      testCase "most negative i32" $+        fmap render (intExp (-2147483648) LitI32) @?= Nothing,+      testCase "largest u32" $+        fmap render (intExp 4294967295 LitU32) @?= Just "4294967295u",+      testCase "u32 overflow" $+        fmap render (intExp 4294967296 LitU32) @?= Nothing,+      testCase "negative u32" $+        fmap render (intExp (-1) LitU32) @?= Nothing,+      testCase "largest Nothing" $+        fmap render (intExp maxBound AbstractInt) @?= Just "9223372036854775807",+      testCase "Nothing overflow" $+        fmap render (intExp minBound AbstractInt) @?= Nothing+    ]++-- | WGSL has no literal for infinities or NaN, and the magnitude of a literal+-- must not exceed the largest finite value of its type.  See+-- https://www.w3.org/TR/WGSL/#floating-point-types.+floatLitTests :: TestTree+floatLitTests =+  testGroup+    "floatExp"+    [ testCase "no suffix" $+        fmap render (floatExp 1.5 AbstractFloat) @?= Just "1.5",+      testCase "f suffix" $+        fmap render (floatExp 1.5 LitF32) @?= Just "1.5f",+      testCase "h suffix" $+        fmap render (floatExp 1.5 LitF16) @?= Just "1.5h",+      testCase "negated literal" $+        fmap render (floatExp (-1.5) LitF32) @?= Just "-1.5f",+      testCase "exponent" $+        fmap render (floatExp 1.0e-2 LitF32) @?= Just "1.0e-2f",+      testCase "infinity" $+        fmap render (floatExp (1 / 0) AbstractFloat) @?= Nothing,+      testCase "negative infinity" $+        fmap render (floatExp (-1 / 0) LitF32) @?= Nothing,+      testCase "NaN" $+        fmap render (floatExp (0 / 0) LitF32) @?= Nothing,+      testCase "largest f32" $+        fmap render (floatExp 3.40282346638528859812e38 LitF32)+          @?= Just "3.4028234663852886e38f",+      testCase "f32 overflow" $+        fmap render (floatExp 1.0e300 LitF32) @?= Nothing,+      testCase "f64 magnitude is an Nothing" $+        fmap render (floatExp 1.0e300 AbstractFloat) @?= Just "1.0e300",+      testCase "largest f16" $+        fmap render (floatExp 65504 LitF16) @?= Just "65504.0h",+      testCase "f16 overflow" $+        fmap render (floatExp 65505 LitF16) @?= Nothing+    ]++-- | An assignment's left-hand side is an assignable expression.  See+-- https://www.w3.org/TR/WGSL/#assignment.+assignTests :: TestTree+assignTests =+  testGroup+    "Assign"+    [ testCase "variable" $+        render (Assign (ExpIdent "x") AssignSimple (ExpIdent "y")) @?= "x = y;",+      testCase "dereference" $+        render (Assign (ExpUnary Indirection (ExpIdent "p")) AssignSimple (ExpIdent "x")) @?= "(*p) = x;",+      testCase "index" $+        render (Assign (ExpIndex (ExpIdent "a") (ExpInt 0 AbstractInt)) AssignSimple (ExpInt 1 AbstractInt))+          @?= "a[0] = 1;",+      testCase "member" $+        render (Assign (ExpMember (ExpIdent "s") "f") AssignSimple (ExpIdent "y")) @?= "s.f = y;"+    ]++-- | A return statement supplies a value exactly when the enclosing function+-- has a return type.  See https://www.w3.org/TR/WGSL/#return-statement.+returnTests :: TestTree+returnTests =+  testGroup+    "Return"+    [ testCase "without value" $+        render (Return Nothing) @?= "return;",+      testCase "with value" $+        render (Return (Just (ExpIdent "x"))) @?= "return x;"+    ]++-- | A function declaration has at most one return type, which may carry+-- attributes.  See https://www.w3.org/TR/WGSL/#function-declaration-sec.+funDeclTests :: TestTree+funDeclTests =+  testGroup+    "FunctionDecl"+    [ testCase "without return type" $+        render (FunctionDecl "f" [] [Param [] "a" I32] Nothing [Call "g" [] [ExpIdent "a"]])+          @?= "fn f(\n  a : i32\n) {\n  g(a);\n}",+      testCase "with return type" $+        render+          (FunctionDecl "f" [] [] (Just ([], I32)) [Return (Just (ExpInt 1 AbstractInt))])+          @?= "fn f() -> i32 {\n  return 1;\n}",+      testCase "empty body" $+        render (FunctionDecl "f" [] [] Nothing []) @?= "fn f() { }",+      testCase "with return attributes" $+        render+          ( FunctionDecl+              "vs"+              [Attribute "vertex" []]+              []+              (Just ([Attribute "builtin" [ExpIdent "position"]], Vec4 F32))+              [Return (Just (ExpIdent "p"))]+          )+          @?= "@vertex\nfn vs() -> @builtin(position) vec4<f32> {\n  return p;\n}"+    ]++sequenceTests :: TestTree+sequenceTests =+  testGroup+    "sequences"+    [ testCase "semicolons" $+        renderBody [Call "f" [] [], Let "x" Nothing one, Call "g" [] []]+          @?= "f();\nlet x = 1;\ng();",+      -- The separator used to end up inside the comment.+      testCase "comment" $+        renderBody [Call "f" [] [], Comment "note", Call "g" [] []]+          @?= "f();\n// note\ng();",+      testCase "compound statement" $+        renderBody [If (ExpIdent "c") [Call "f" [] []] [] Nothing, Call "g" [] []]+          @?= "if c {\n  f();\n}\ng();",+      -- An empty body used to render as a stray empty statement.+      testCase "empty body" $+        render (While (ExpIdent "c") []) @?= "while c { }",+      testCase "empty statement" $+        render (While (ExpIdent "c") [Empty]) @?= "while c {\n  ;\n}",+      testCase "block introduces a scope" $+        render (Compound [Let "x" Nothing one]) @?= "{\n  let x = 1;\n}",+      -- An absent else clause is not the same as an empty one.+      testCase "absent else" $+        render (If (ExpIdent "c") [] [] Nothing) @?= "if c { }",+      testCase "empty else" $+        render (If (ExpIdent "c") [] [] (Just [])) @?= "if c { } else { }"+    ]++-- | A for loop's update is an assignment or a call. See+-- https://www.w3.org/TR/WGSL/#for-statement.+forTests :: TestTree+forTests =+  testGroup+    "For"+    [ testCase "assignment update" $+        render+          ( For+              "i"+              one+              (ExpBinary Less (ExpIdent "i") one)+              (UpdateAssign (ExpIdent "i") AssignSimple one)+              [Call "f" [] []]+          )+          @?= "for (var i = 1; (i < 1); i = 1) {\n  f();\n}",+      testCase "call update" $+        render (For "i" one (ExpIdent "c") (UpdateCall "step" [] [ExpIdent "i"]) [])+          @?= "for (var i = 1; c; step(i)) { }"+    ]++-- | A callee may carry a template argument list.  See+-- https://www.w3.org/TR/WGSL/#syntax-call_phrase.+callTests :: TestTree+callTests =+  testGroup+    "ExpCall"+    [ testCase "without template arguments" $+        render (ExpCall "f" [] [ExpIdent "x"]) @?= "f(x)",+      testCase "without arguments" $+        render (ExpCall "atomicLoad" [] []) @?= "atomicLoad()",+      testCase "with a type argument" $+        render (ExpCall "vec2" [ExpType F32] [one, one])+          @?= "vec2<f32>(1, 1)",+      testCase "bitcast" $+        render (bitcast I32 (ExpIdent "x")) @?= "bitcast<i32>(x)",+      -- The template argument list end is disambiguated by the grammar, so a+      -- nested list needs no space between the closing delimiters.+      testCase "nested template arguments" $+        render (bitcast (Vec2 F16) (ExpIdent "x"))+          @?= "bitcast<vec2<f16>>(x)"+    ]++-- | The alignment and size table.  See+-- https://www.w3.org/TR/WGSL/#alignment-and-size.+layoutTests :: TestTree+layoutTests =+  testGroup+    "alignOf and sizeOf"+    [ -- bool is not host-shareable, but it does have a fixed footprint.+      testCase "bool" $ layout Bool @?= Just (4, 4),+      testCase "i32" $ layout I32 @?= Just (4, 4),+      testCase "f16" $ layout F16 @?= Just (2, 2),+      testCase "atomic" $ layout (Atomic I32) @?= Just (4, 4),+      testCase "vec2<f32>" $ layout (Vec2 F32) @?= Just (8, 8),+      testCase "vec3<f32>" $ layout (Vec3 F32) @?= Just (16, 12),+      testCase "vec4<f32>" $ layout (Vec4 F32) @?= Just (16, 16),+      testCase "vec2<f16>" $ layout (Vec2 F16) @?= Just (4, 4),+      testCase "vec3<f16>" $ layout (Vec3 F16) @?= Just (8, 6),+      testCase "vec4<f16>" $ layout (Vec4 F16) @?= Just (8, 8),+      testCase "named type" $ layout (Named "S") @?= Nothing,+      testCase "pointer" $ layout (Ptr I32 Function Nothing) @?= Nothing,+      testCase "texture" $ layout (TextureSampled Tex2d F32) @?= Nothing,+      testCase "sampler" $ layout Sampler @?= Nothing+    ]++-- | Texture and sampler types are opaque: they have no layout, and are not+-- host-shareable.  There is no other way to spell them, so no type that does+-- have a layout can be mistaken for one.  See+-- https://www.w3.org/TR/WGSL/#texture-sampler-types.+opaqueTests :: TestTree+opaqueTests =+  testGroup+    "textures and samplers"+    [ testCase "no layout" $+        map layout opaques @?= map (const Nothing) opaques,+      testCase "not host-shareable" $+        filter hostShareable opaques @?= [],+      testCase "sampled" $+        map+          render+          [ TextureSampled Tex1d F32,+            TextureSampled Tex2d F32,+            TextureSampled Tex2dArray I32,+            TextureSampled Tex3d U32,+            TextureSampled TexCube F32,+            TextureSampled TexCubeArray U32+          ]+          @?= [ "texture_1d<f32>",+                "texture_2d<f32>",+                "texture_2d_array<i32>",+                "texture_3d<u32>",+                "texture_cube<f32>",+                "texture_cube_array<u32>"+              ],+      testCase "multisampled" $+        render (TextureMultisampled F32) @?= "texture_multisampled_2d<f32>",+      testCase "external" $ render TextureExternal @?= "texture_external",+      testCase "depth" $+        map+          render+          [ TextureDepth Depth2d,+            TextureDepth Depth2dArray,+            TextureDepth DepthCube,+            TextureDepth DepthCubeArray,+            TextureDepth DepthMultisampled2d+          ]+          @?= [ "texture_depth_2d",+                "texture_depth_2d_array",+                "texture_depth_cube",+                "texture_depth_cube_array",+                "texture_depth_multisampled_2d"+              ],+      testCase "storage" $+        map+          render+          [ TextureStorage StorageTex1d Rgba8Unorm Write,+            TextureStorage StorageTex2d R32Float Read,+            TextureStorage StorageTex2dArray Rgb10a2Uint ReadWrite,+            TextureStorage StorageTex3d Rg11b10Ufloat Write+          ]+          @?= [ "texture_storage_1d<rgba8unorm, write>",+                "texture_storage_2d<r32float, read>",+                "texture_storage_2d_array<rgb10a2uint, read_write>",+                "texture_storage_3d<rg11b10ufloat, write>"+              ],+      testCase "samplers" $+        map render [Sampler, SamplerComparison] @?= ["sampler", "sampler_comparison"]+    ]++-- | One of every texture and sampler family.+opaques :: [Type]+opaques =+  [ TextureSampled Tex2d F32,+    TextureMultisampled F32,+    TextureExternal,+    TextureStorage StorageTex2d Rgba8Unorm Write,+    TextureDepth Depth2d,+    Sampler,+    SamplerComparison+  ]++-- | A matrix is laid out as an array of its columns, so @matCxR@ has the+-- alignment of @vecR@ and the size of @array<vecR, C>@.  See+-- https://www.w3.org/TR/WGSL/#alignment-and-size.+matrixLayoutTests :: TestTree+matrixLayoutTests =+  testGroup+    "matrices"+    [ testCase "mat2x2<f32>" $ layout (Mat2x2 F32) @?= Just (8, 16),+      testCase "mat3x2<f32>" $ layout (Mat3x2 F32) @?= Just (8, 24),+      testCase "mat4x2<f32>" $ layout (Mat4x2 F32) @?= Just (8, 32),+      -- The columns are padded from 12 bytes to a stride of 16.+      testCase "mat2x3<f32>" $ layout (Mat2x3 F32) @?= Just (16, 32),+      testCase "mat3x3<f32>" $ layout (Mat3x3 F32) @?= Just (16, 48),+      testCase "mat4x3<f32>" $ layout (Mat4x3 F32) @?= Just (16, 64),+      testCase "mat2x4<f32>" $ layout (Mat2x4 F32) @?= Just (16, 32),+      testCase "mat3x4<f32>" $ layout (Mat3x4 F32) @?= Just (16, 48),+      testCase "mat4x4<f32>" $ layout (Mat4x4 F32) @?= Just (16, 64),+      testCase "mat2x2<f16>" $ layout (Mat2x2 F16) @?= Just (4, 8),+      testCase "mat4x2<f16>" $ layout (Mat4x2 F16) @?= Just (4, 16),+      testCase "mat2x3<f16>" $ layout (Mat2x3 F16) @?= Just (8, 16),+      testCase "mat4x3<f16>" $ layout (Mat4x3 F16) @?= Just (8, 32),+      testCase "mat4x4<f16>" $ layout (Mat4x4 F16) @?= Just (8, 32)+    ]++-- | At least one type of every shape, for properties that should hold of all+-- of them.+representativeTypes :: [Type]+representativeTypes =+  [Bool, I32, U32, F16, F32]+    ++ [Vec2 F32, Vec3 F32, Vec4 F32, Vec2 F16]+    ++ [ Mat2x2 F32,+         Mat2x3 F32,+         Mat2x4 F32,+         Mat3x2 F32,+         Mat3x3 F32,+         Mat3x4 F32,+         Mat4x2 F32,+         Mat4x3 F32,+         Mat4x4 F32,+         Mat2x2 F16+       ]+    ++ [Atomic I32, Atomic U32]+    ++ [ Array F32 Nothing,+         Array F32 (Just (ExpInt 8 AbstractInt)),+         Array F32 (Just (ExpInt 8 LitI32)),+         Array (Named "S") (Just (ExpInt 3 AbstractInt)),+         Array (Array F32 (Just (ExpInt 2 AbstractInt))) Nothing+       ]+    ++ [ Ptr I32 Function Nothing,+         Ptr I32 Function (Just ReadWrite),+         Ptr I32 Storage (Just Read),+         Ptr (Array F32 Nothing) Private Nothing+       ]+    ++ [Named "S", Named "T"]+    ++ [ TextureSampled Tex1d F32,+         TextureSampled Tex2d F32,+         TextureSampled Tex2d I32,+         TextureSampled Tex2dArray F32,+         TextureSampled Tex3d F32,+         TextureSampled TexCube F32,+         TextureSampled TexCubeArray F32,+         TextureMultisampled F32,+         TextureExternal,+         TextureStorage StorageTex1d Rgba8Unorm Write,+         TextureStorage StorageTex2d Rgba8Unorm Write,+         TextureStorage StorageTex2d R32Float Write,+         TextureStorage StorageTex2d R32Float Read,+         TextureStorage StorageTex2dArray Rgba8Unorm Write,+         TextureStorage StorageTex3d Rgba8Unorm Write,+         TextureDepth Depth2d,+         TextureDepth Depth2dArray,+         TextureDepth DepthCube,+         TextureDepth DepthCubeArray,+         TextureDepth DepthMultisampled2d,+         Sampler,+         SamplerComparison+       ]++-- | Distinct types must have distinct spellings.  Were two of them to spell+-- the same WGSL type, they could disagree about its layout, which is what an+-- open-ended escape hatch in 'Type' used to allow.+injectivityTests :: TestTree+injectivityTests =+  testGroup+    "spellings are distinct"+    [ testCase "the sample has no duplicates" $+        nub representativeTypes @?= representativeTypes,+      testCase "no two types spell the same" $+        let spellings = map render representativeTypes+         in nub spellings @?= spellings+    ]++-- | Every type is spelled as an identifier with an optional template argument+-- list.  See https://www.w3.org/TR/WGSL/#type-specifiers.+typeSpellingTests :: TestTree+typeSpellingTests =+  testGroup+    "spelling"+    [ testCase "scalars" $+        map render [Bool, I32, U32, F16, F32]+          @?= ["bool", "i32", "u32", "f16", "f32"],+      testCase "vector" $ render (Vec3 F32) @?= "vec3<f32>",+      testCase "matrix" $ render (Mat4x3 F16) @?= "mat4x3<f16>",+      testCase "atomic" $ render (Atomic U32) @?= "atomic<u32>",+      testCase "runtime-sized array" $ render (Array F32 Nothing) @?= "array<f32>",+      testCase "fixed-size array" $+        render (Array F32 (Just (ExpInt 8 AbstractInt))) @?= "array<f32, 8>",+      -- Element and store types are no longer restricted to scalars.+      testCase "array of struct" $+        render (Array (Named "S") (Just (ExpInt 3 AbstractInt))) @?= "array<S, 3>",+      testCase "array of array" $+        render+          ( Array+              (Array F32 (Just (ExpInt 2 AbstractInt)))+              (Just (ExpInt 3 AbstractInt))+          )+          @?= "array<array<f32, 2>, 3>",+      -- The specification's own example of a pointer type.+      testCase "pointer to array" $+        render (Ptr (Array F32 (Just (ExpInt 50 AbstractInt))) Private Nothing)+          @?= "ptr<private, array<f32, 50>>",+      testCase "sampler" $ render Sampler @?= "sampler"+    ]++-- | An array's size is its element count times the element stride.  See+-- https://www.w3.org/TR/WGSL/#array-layout-examples.+arrayLayoutTests :: TestTree+arrayLayoutTests =+  testGroup+    "arrays"+    [ testCase "strideOf" $+        map strideOf [F32, Vec3 F32, F16, Named "S"]+          @?= [Just 4, Just 16, Just 2, Nothing],+      testCase "array<f32, 8>" $+        layout (Array F32 (Just (ExpInt 8 AbstractInt))) @?= Just (4, 32),+      -- The element is padded from 12 bytes to a stride of 16.+      testCase "array<vec3<f32>, 8>" $+        layout (Array (Vec3 F32) (Just (ExpInt 8 AbstractInt)))+          @?= Just (16, 128),+      testCase "runtime-sized array" $+        (alignOf (Array F32 Nothing), sizeOf (Array F32 Nothing))+          @?= (Just 4, Nothing),+      testCase "overridable element count" $+        layout (Array F32 (Just (ExpIdent "n"))) @?= Nothing+    ]++-- | See https://www.w3.org/TR/WGSL/#structure-member-layout.+structLayoutTests :: TestTree+structLayoutTests =+  testGroup+    "structures"+    [ -- struct A from the specification's example.+      testCase "implicit padding" $+        structLayout [F32, F32, Vec2 F32, F32]+          @?= (Just [0, 4, 8, 16], Just 8, Just 24),+      -- The first four members of struct B from the same example.+      testCase "vector alignment" $+        structLayout+          [Vec2 F32, Vec3 F32, F32, F32]+          @?= (Just [0, 16, 28, 32], Just 16, Just 48),+      testCase "single member" $+        structLayout [I32] @?= (Just [0], Just 4, Just 4),+      -- The offsets and alignment of a storage buffer's header are known even+      -- though its size is not.+      testCase "trailing runtime-sized array" $+        structLayout [U32, Array F32 Nothing]+          @?= (Just [0, 4], Just 4, Nothing),+      -- A member without a size stops the offsets of later members from+      -- being known, but the alignment still is.+      testCase "member without a size" $+        structLayout [Array F32 Nothing, U32]+          @?= (Nothing, Just 4, Nothing),+      testCase "no members" $+        structLayout [] @?= (Nothing, Nothing, Nothing),+      testCase "member without a layout" $+        structLayout [I32, Named "S"]+          @?= (Nothing, Nothing, Nothing)+    ]++-- | @\@align@ and @\@size@ override a member's alignment and size.  See+-- https://www.w3.org/TR/WGSL/#structure-member-layout.+memberLayoutTests :: TestTree+memberLayoutTests =+  testGroup+    "layout attributes"+    [ testCase "alignOfMember" $+        ( alignOfMember (StructMember [] "m" F32),+          alignOfMember (StructMember [Attribute "align" [ExpInt 16 AbstractInt]] "m" F32)+        )+          @?= (Just 4, Just 16),+      testCase "sizeOfMember" $+        ( sizeOfMember (StructMember [] "m" F32),+          sizeOfMember (StructMember [Attribute "size" [ExpInt 16 AbstractInt]] "m" F32)+        )+          @?= (Just 4, Just 16),+      testCase "unrelated attribute" $+        alignOfMember (StructMember [Attribute "location" [ExpInt 0 AbstractInt]] "m" F32)+          @?= Just 4,+      -- The argument of @align or @size need only be a const-expression, and+      -- one that is not a literal leaves the member's layout unknown.  It+      -- must not fall back to the layout of the member's type.+      testCase "alignment that is not a literal" $+        ( alignOfMember (StructMember [Attribute "align" [ExpIdent "N"]] "m" F32),+          sizeOfMember (StructMember [Attribute "size" [ExpIdent "N"]] "m" F32)+        )+          @?= (Nothing, Nothing),+      testCase "structure with an unknown member alignment" $+        structLayoutOf+          [ StructMember [] "a" F32,+            StructMember [Attribute "align" [ExpIdent "N"]] "b" F32+          ]+          @?= (Nothing, Nothing, Nothing),+      -- struct A from the specification's example with explicit member sizes:+      -- the @size(16) on the last member grows the structure from 24 to 32.+      testCase "explicit size" $+        structLayoutOf+          [ StructMember [] "u" F32,+            StructMember [] "v" F32,+            StructMember [] "w" (Vec2 F32),+            StructMember [Attribute "size" [ExpInt 16 AbstractInt]] "x" F32+          ]+          @?= (Just [0, 4, 8, 16], Just 8, Just 32),+      -- The same example's struct B places its @align(16) member at offset 48+      -- rather than 40.+      testCase "explicit alignment" $+        structLayoutOf+          [ StructMember [] "a" (Vec2 F32),+            StructMember [] "b" (Vec3 F32),+            StructMember [] "c" F32,+            StructMember [] "d" F32,+            StructMember [Attribute "align" [ExpInt 16 AbstractInt]] "e" F32+          ]+          @?= (Just [0, 16, 28, 32, 48], Just 16, Just 64)+    ]++-- | A structure member may carry attributes, and so may an override+-- declaration.+attributeTests :: TestTree+attributeTests =+  testGroup+    "attributes"+    [ testCase "structure member" $+        render+          ( StructDecl+              "S"+              [ StructMember [] "a" I32,+                StructMember [Attribute "size" [ExpInt 16 AbstractInt]] "b" F32+              ]+          )+          @?= "struct S {\n  a : i32,\n  @size(16) b : f32\n}",+      testCase "override identifier" $+        render+          (GlobalOverride [Attribute "id" [ExpInt 7 AbstractInt]] "n" (Just I32) Nothing)+          @?= "@id(7)\noverride n : i32;",+      testCase "override without attributes" $+        render (GlobalOverride [] "n" (Just I32) (Just (ExpInt 0 AbstractInt)))+          @?= "override n : i32 = 0;"+    ]++-- | See https://www.w3.org/TR/WGSL/#host-shareable-types.+hostShareableTests :: TestTree+hostShareableTests =+  testGroup+    "hostShareable"+    [ testCase "numeric scalar" $ hostShareable I32 @?= True,+      testCase "bool" $ hostShareable Bool @?= False,+      testCase "numeric vector" $ hostShareable (Vec3 F32) @?= True,+      testCase "vector of bool" $ hostShareable (Vec3 Bool) @?= False,+      testCase "atomic" $ hostShareable (Atomic I32) @?= True,+      testCase "runtime-sized array" $+        hostShareable (Array F32 Nothing) @?= True,+      testCase "fixed-size array" $+        hostShareable (Array F32 (Just (ExpInt 8 AbstractInt))) @?= True,+      testCase "overridable element count" $+        hostShareable (Array F32 (Just (ExpIdent "n"))) @?= False,+      testCase "named type" $ hostShareable (Named "S") @?= False,+      testCase "pointer" $+        hostShareable (Ptr I32 Function Nothing) @?= False+    ]++-- | Operators are a closed set, spelled as the grammar spells them.  See+-- https://www.w3.org/TR/WGSL/#expressions.+operatorTests :: TestTree+operatorTests =+  testGroup+    "operators"+    [ testCase "binary" $+        map+          (\op -> render (ExpBinary op (ExpIdent "a") (ExpIdent "b")))+          [ Add,+            Subtract,+            Multiply,+            Divide,+            Remainder,+            BitwiseAnd,+            BitwiseOr,+            BitwiseExclusiveOr,+            ShiftLeft,+            ShiftRight,+            Less,+            LessEqual,+            Greater,+            GreaterEqual,+            Equal,+            NotEqual,+            ShortCircuitAnd,+            ShortCircuitOr+          ]+          @?= [ "(a + b)",+                "(a - b)",+                "(a * b)",+                "(a / b)",+                "(a % b)",+                "(a & b)",+                "(a | b)",+                "(a ^ b)",+                "(a << b)",+                "(a >> b)",+                "(a < b)",+                "(a <= b)",+                "(a > b)",+                "(a >= b)",+                "(a == b)",+                "(a != b)",+                "(a && b)",+                "(a || b)"+              ],+      testCase "unary" $+        map+          (\op -> render (ExpUnary op (ExpIdent "a")))+          [Negate, LogicalNegate, BitwiseComplement, Indirection, AddressOf]+          @?= ["(-a)", "(!a)", "(~a)", "(*a)", "(&a)"]+    ]++-- | Directives precede all declarations in a module.  See+-- https://www.w3.org/TR/WGSL/#directives.+directiveTests :: TestTree+directiveTests =+  testGroup+    "directives"+    [ testCase "enable" $+        render (Enable (EnableF16 :| [])) @?= "enable f16;",+      testCase "enable several" $+        render (Enable (EnableF16 :| [EnableSubgroups]))+          @?= "enable f16, subgroups;",+      testCase "every enable extension" $+        map+          (render . (\e -> Enable (e :| [])))+          [ EnableF16,+            EnableClipDistances,+            EnableDualSourceBlending,+            EnableSubgroups,+            EnablePrimitiveIndex,+            EnableSubgroupSizeControl+          ]+          @?= [ "enable f16;",+                "enable clip_distances;",+                "enable dual_source_blending;",+                "enable subgroups;",+                "enable primitive_index;",+                "enable subgroup_size_control;"+              ],+      testCase "requires" $+        render (Requires (TextureFormatsTier1 :| [PointerCompositeAccess]))+          @?= "requires texture_formats_tier1, pointer_composite_access;",+      testCase "diagnostic" $+        render (Diagnostic Off "derivative_uniformity")+          @?= "diagnostic(off, derivative_uniformity);",+      testCase "severities" $+        map (render . (`Diagnostic` "r")) [Error, Warning, Info, Off]+          @?= [ "diagnostic(error, r);",+                "diagnostic(warning, r);",+                "diagnostic(info, r);",+                "diagnostic(off, r);"+              ]+    ]++-- | A module is its directives followed by its declarations.  The f16 type+-- cannot legally be used without the directive that enables it, which is the+-- reason a module needs a representation of its own.+moduleTests :: TestTree+moduleTests =+  testGroup+    "Module"+    [ testCase "directives first" $+        render+          ( Module+              [Enable (EnableF16 :| [])]+              [GlobalVariable [] Private Nothing "x" (Just F16) Nothing]+          )+          @?= "enable f16;\nvar<private> x : f16;",+      testCase "without directives" $+        render (Module [] [GlobalVariable [] Private Nothing "x" (Just F32) Nothing])+          @?= "var<private> x : f32;",+      testCase "const_assert" $+        render (Module [] [GlobalAssert (ExpIdent "c")])+          @?= "const_assert c;",+      testCase "structure" $+        render+          ( Module+              []+              [GlobalStruct (StructDecl "S" [StructMember [] "a" I32, StructMember [] "b" F32])]+          )+          @?= "struct S {\n  a : i32,\n  b : f32\n}"+    ]++-- | An @if@ may be followed by any number of @else if@ clauses.  See+-- https://www.w3.org/TR/WGSL/#if-statement.+elseIfTests :: TestTree+elseIfTests =+  testGroup+    "else if"+    [ testCase "one clause" $+        render (If (ExpIdent "a") [Break] [(ExpIdent "b", [Continue])] Nothing)+          @?= "if a {\n  break;\n} else if b {\n  continue;\n}",+      testCase "chain with else" $+        render+          ( If+              (ExpIdent "a")+              [Break]+              [(ExpIdent "b", [Continue]), (ExpIdent "c", [Discard])]+              (Just [Return Nothing])+          )+          @?= "if a {\n  break;\n} else if b {\n  continue;\n}"+            <> " else if c {\n  discard;\n} else {\n  return;\n}"+    ]++-- | See https://www.w3.org/TR/WGSL/#switch-statement.+switchTests :: TestTree+switchTests =+  testGroup+    "switch"+    [ testCase "several selectors" $+        render (Switch (ExpIdent "x") (Case (CaseExp one :| [CaseExp two]) [Break] :| []))+          @?= "switch x {\n  case 1, 2: {\n    break;\n  }\n}",+      -- 'default' may appear among a case clause's selectors, as well as+      -- being a clause of its own.+      testCase "default as a selector" $+        render (Switch (ExpIdent "x") (Case (CaseExp one :| [CaseDefault]) [] :| []))+          @?= "switch x {\n  case 1, default: { }\n}",+      testCase "default clause" $+        render (Switch (ExpIdent "x") (DefaultCase [Discard] :| []))+          @?= "switch x {\n  default: {\n    discard;\n  }\n}",+      testCase "several clauses" $+        render+          ( Switch+              (ExpIdent "x")+              (Case (CaseExp one :| []) [Break] :| [DefaultCase []])+          )+          @?= "switch x {\n  case 1: {\n    break;\n  }\n  default: { }\n}"+    ]++-- | A @loop@ may end in a @continuing@ block, which may end in a @break if@.+-- See https://www.w3.org/TR/WGSL/#loop-statement.+loopTests :: TestTree+loopTests =+  testGroup+    "loop"+    [ testCase "without continuing" $+        render (Loop [Break] Nothing) @?= "loop {\n  break;\n}",+      testCase "with continuing" $+        render (Loop [Call "f" [] []] (Just (Continuing [Assign (ExpIdent "i") AssignSimple one] Nothing)))+          @?= "loop {\n  f();\n  continuing {\n    i = 1;\n  }\n}",+      testCase "with break if" $+        render (Loop [] (Just (Continuing [] (Just (ExpIdent "done")))))+          @?= "loop {\n  continuing {\n    break if done;\n  }\n}",+      testCase "jumps" $+        map render [Break, Continue, Discard]+          @?= ["break;", "continue;", "discard;"]+    ]++-- | An assignment carries an operator, which may be a compound one.  See+-- https://www.w3.org/TR/WGSL/#assignment.+assignOpTests :: TestTree+assignOpTests =+  testGroup+    "assignment operators"+    [ testCase "every operator" $+        map+          (\op -> render (Assign (ExpIdent "x") op one))+          [ AssignSimple,+            AssignAdd,+            AssignSubtract,+            AssignMultiply,+            AssignDivide,+            AssignRemainder,+            AssignBitwiseAnd,+            AssignBitwiseOr,+            AssignBitwiseExclusiveOr,+            AssignShiftLeft,+            AssignShiftRight+          ]+          @?= [ "x = 1;",+                "x += 1;",+                "x -= 1;",+                "x *= 1;",+                "x /= 1;",+                "x %= 1;",+                "x &= 1;",+                "x |= 1;",+                "x ^= 1;",+                "x <<= 1;",+                "x >>= 1;"+              ],+      testCase "phony assignment" $+        render (PhonyAssign (ExpCall "f" [] [])) @?= "_ = f();",+      testCase "increment and decrement" $+        map render [Increment (ExpIdent "x"), Decrement (ExpIndex (ExpIdent "a") one)]+          @?= ["x++;", "a[1]--;"],+      testCase "const_assert" $+        render (ConstAssert (ExpIdent "c")) @?= "const_assert c;"+    ]++-- | A @let@, @const@ or @var@ names an optionally typed identifier, and a+-- @var@ may have an initialiser.  See+-- https://www.w3.org/TR/WGSL/#var-and-value.+localDeclTests :: TestTree+localDeclTests =+  testGroup+    "local declarations"+    [ testCase "let" $+        map render [Let "a" Nothing one, Let "a" (Just F32) one]+          @?= ["let a = 1;", "let a : f32 = 1;"],+      testCase "const" $+        map render [Const "a" Nothing one, Const "a" (Just F32) one]+          @?= ["const a = 1;", "const a : f32 = 1;"],+      testCase "var" $+        map+          render+          [ Var "a" (Just I32) Nothing,+            Var "a" (Just I32) (Just one),+            Var "a" Nothing (Just one)+          ]+          @?= ["var a : i32;", "var a : i32 = 1;", "var a = 1;"]+    ]++-- | The update of a @for@ loop may be any of the variable-updating+-- statements, or a call.  See https://www.w3.org/TR/WGSL/#for-statement.+forUpdateTests :: TestTree+forUpdateTests =+  testGroup+    "for updates"+    [ testCase "every form" $+        map+          (\u -> render (For "i" one (ExpIdent "c") u []))+          [ UpdateAssign (ExpIdent "i") AssignAdd one,+            UpdatePhonyAssign (ExpIdent "i"),+            UpdateIncrement (ExpIdent "i"),+            UpdateDecrement (ExpIdent "i"),+            UpdateCall "step" [] [ExpIdent "i"]+          ]+          @?= [ "for (var i = 1; c; i += 1) { }",+                "for (var i = 1; c; _ = i) { }",+                "for (var i = 1; c; i++) { }",+                "for (var i = 1; c; i--) { }",+                "for (var i = 1; c; step(i)) { }"+              ]+    ]++-- | The remaining module-scope declarations.  See+-- https://www.w3.org/TR/WGSL/#module-scope-declarations.+globalDeclTests :: TestTree+globalDeclTests =+  testGroup+    "other declarations"+    [ testCase "alias" $+        render (GlobalAlias "i16" I32) @?= "alias i16 = i32;",+      testCase "const" $+        map render [GlobalConst "N" Nothing one, GlobalConst "N" (Just I32) one]+          @?= ["const N = 1;", "const N : i32 = 1;"],+      -- A module-scope variable's type is optional, and one in the private+      -- address space may have an initialiser.+      testCase "variable initialiser" $+        map+          render+          [ GlobalVariable [] Private Nothing "x" (Just F32) Nothing,+            GlobalVariable [] Private Nothing "x" Nothing (Just one),+            GlobalVariable [] Private Nothing "x" (Just F32) (Just one)+          ]+          @?= ["var<private> x : f32;", "var<private> x = 1;", "var<private> x : f32 = 1;"],+      testCase "override without a type" $+        render (GlobalOverride [] "k" Nothing (Just one)) @?= "override k = 1;"+    ]++-- | An index may be applied to any expression, and chained.  Swizzles use+-- the same slot as member access, so 'ExpMember' spells them.  See+-- https://www.w3.org/TR/WGSL/#component-reference-expr.+indexTests :: TestTree+indexTests =+  testGroup+    "indexing"+    [ testCase "identifier" $+        render (ExpIndex (ExpIdent "a") (ExpIdent "i")) @?= "a[i]",+      -- Indexing a nested array needs an index of an index.+      testCase "chained" $+        render (ExpIndex (ExpIndex (ExpIdent "a") (ExpIdent "i")) (ExpIdent "j"))+          @?= "a[i][j]",+      testCase "call result" $+        render (ExpIndex (ExpCall "f" [] [ExpIdent "x"]) (ExpIdent "i")) @?= "f(x)[i]",+      testCase "through a pointer" $+        render (ExpIndex (ExpUnary Indirection (ExpIdent "p")) (ExpIdent "i"))+          @?= "(*p)[i]",+      testCase "member" $+        render (ExpIndex (ExpMember (ExpIdent "s") "f") (ExpIdent "i")) @?= "s.f[i]",+      testCase "swizzle of an index" $+        render (ExpMember (ExpIndex (ExpIdent "a") (ExpIdent "i")) "xyz") @?= "a[i].xyz",+      testCase "as an assignment target" $+        render+          ( Assign+              (ExpIndex (ExpIndex (ExpIdent "a") (ExpIdent "i")) (ExpIdent "j"))+              AssignSimple+              one+          )+          @?= "a[i][j] = 1;"+    ]++-- | An identifier matches the identifier pattern, is neither a keyword nor a+-- reserved word, and does not begin with two underscores.  See+-- https://www.w3.org/TR/WGSL/#identifiers.+identTests :: TestTree+identTests =+  testGroup+    "validIdent"+    [ testCase "accepted" $+        filter (not . validIdent) ["x", "A", "a1", "_a", "a_", "foo_bar"]+          @?= [],+      testCase "empty" $ validIdent "" @?= False,+      -- A lone underscore is not an identifier, and neither is a name+      -- beginning with two of them.+      testCase "underscores" $+        filter validIdent ["_", "__", "__a", "__frexp_result_f32"] @?= [],+      testCase "pattern" $+        filter validIdent ["1a", "a-b", "a b", "a.b"] @?= [],+      testCase "keywords" $ filter validIdent keywords @?= [],+      testCase "reserved words" $ filter validIdent reservedWords @?= [],+      testCase "list sizes" $+        (length keywords, length reservedWords) @?= (26, 146)+    ]++tests :: TestTree+tests =+  testGroup+    "Language.WGSL"+    [ testGroup "identifiers" [identTests],+      testGroup "types" [typeSpellingTests, injectivityTests, pointerTests],+      testGroup+        "memory"+        [ accessModeTests,+          layoutTests,+          matrixLayoutTests,+          opaqueTests,+          arrayLayoutTests,+          structLayoutTests,+          memberLayoutTests,+          hostShareableTests+        ],+      testGroup+        "expressions"+        [operatorTests, intLitTests, floatLitTests, callTests, indexTests],+      testGroup+        "statements"+        [ sequenceTests,+          assignTests,+          returnTests,+          forTests,+          elseIfTests,+          switchTests,+          loopTests,+          assignOpTests,+          localDeclTests,+          forUpdateTests+        ],+      testGroup+        "declarations"+        [ globalVarTests,+          globalDeclTests,+          funDeclTests,+          attributeTests,+          directiveTests,+          moduleTests+        ]+    ]++main :: IO ()+main = defaultMain tests