Compare commits

4 Commits

Author SHA1 Message Date
mtgmonkey
f836081564 modularize 2025-12-25 15:03:06 +01:00
mtgmonkey
38c7580cc4 add CHANGELOG.md 2025-12-22 22:37:46 +01:00
mtgmonkey
67202e0f41 remove old files 2025-12-22 16:02:02 +01:00
mtgmonkey
298a13b60d init neue 2025-12-22 15:58:44 +01:00
15 changed files with 699 additions and 364 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
result
dist-newstyle

44
CHANGELOG.md Normal file
View File

@@ -0,0 +1,44 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Eventual]
### Added
### Changed
## [0.2.0] - 2025-12-25
### Added
- 100% documentation coverage
- `haskellPackages.hlint` in `flake.nix` dev shell
- `README.md`
### Changed
- split into the following modules
- `GLSL`
- `GLSL.Compile`
- `GLSL.Internal`
- `GLSL.Internal.Compile`
- only the former 2 have type-safe gurantees for generated code
- most projects should only import the former 2
- see sample in `Main.hs`
### Fixed
- issue where user was expected to correctly type variable declarations
- user must now only use the correct constructor
- old: `someVar = var (id "some_var") T'float :: Exp T'float`
- new: `someVar = var'float $ id "some_var"`
## [0.1.0] - 2025-12-21
- initialized project
- added `CHANGELOG.md`

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
Copyright (c) 2025, andromeda
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1 +1,30 @@
generate type-safe GLSL with this Haskell grammar. Under heavy development.
## installation
#### with Nix:
```bash
nix run git+https://git.mtgmonkey.net/Andromeda/hs-glsl
```
#### without Nix:
```bash
git clone https://git.mtgmonkey.net/Andromeda/hs-glsl
cd hs-glsl
cabal build
```
## usage
see `src/Main.hs` for an example
## development
#### with Nix
```bash
git clone https://git.mtgmonkey.net/Andromeda/hs-glsl
cd hs-glsl
nix develop
```

8
flake.lock generated
View File

@@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1764242076,
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"lastModified": 1766125104,
"narHash": "sha256-l/YGrEpLromL4viUo5GmFH3K5M1j0Mb9O+LiaeCPWEM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"rev": "7d853e518814cca2a657b72eeba67ae20ebf7059",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"ref": "nixpkgs-unstable",
"type": "indirect"
}
},

View File

@@ -1,13 +1,29 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
};
outputs = {nixpkgs, ...}: let
outputs = {
nixpkgs,
self,
...
}: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system} = {
default = pkgs.callPackage ./package.nix {};
default =
pkgs.haskellPackages.callCabal2nix "hs-glsl" ./. {};
};
devShells.${system} = {
default = pkgs.mkShell {
packages = [
pkgs.cabal-install
pkgs.haskellPackages.hlint
];
inputsFrom = [
self.packages.${system}.default
];
};
};
};
}

33
hs-glsl.cabal Normal file
View File

@@ -0,0 +1,33 @@
cabal-version: 3.0
name: hs-glsl
version: 0.2.0
homepage: https://git.mtgmonkey.net/Andromeda/hs-glsl
license: BSD-3-Clause
license-file: LICENSE
author: andromeda
maintainer: @Andromeda:tchncs.de
category: Graphics
build-type: Simple
common warnings
ghc-options:
-Wall
-Werror
library
import: warnings
build-depends: base >=4.20
hs-source-dirs: src
exposed-modules: GLSL
, GLSL.Compile
, GLSL.Internal
, GLSL.Internal.Compile
default-language: Haskell2010
executable hs-glsl
import: warnings
main-is: Main.hs
build-depends: base >=4.20
hs-source-dirs: src
other-modules: GLSL
, GLSL.Compile
, GLSL.Internal
, GLSL.Internal.Compile
default-language: Haskell2010

View File

@@ -1,38 +0,0 @@
{
haskellPackages,
lib,
stdenv,
...
}: let
ghcExeOptions = "-O -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N -main-is Language.GLSL.Examples.main";
ghcPackages = p: [
p.relude
p.pretty-simple
];
in
stdenv.mkDerivation {
pname = "hs-glsl";
version = "0.1.0";
src = ./src;
nativeBuildInputs = [
(haskellPackages.ghcWithPackages ghcPackages)
];
buildInputs = [
];
configurePhase = ''
'';
buildPhase = ''
ghc ${ghcExeOptions} ./Language/GLSL/Examples.hs -o ./Main
'';
installPhase = ''
mkdir -p $out/bin
cp ./Main $out/bin/hs-glsl
'';
meta = {
homepage = "https://mtgmonkey.net";
license = lib.licenses.wtfpl;
mainProgram = "hs-glsl";
platforms = ["x86_64-linux"];
};
}

261
src/GLSL.hs Normal file
View File

@@ -0,0 +1,261 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{- |
- Module : GLSL
- Description : DSEL of GLSL
- Copyright : 2025 Andromeda
- License : BSD 3-Clause
- Maintainer : @Andromeda:tchncs.de
- Stability : Experimental
-}
module GLSL
( float
, bool
, vec2
, vec3
, vec4
, normalize
, mul
, add
, dec_var
, var'float
, var'bool
, var'vec2
, var'vec3
, var'vec4
, var'mat4
, GLSL.id
, mkShaders
, Type (..)
, T'float
, T'bool
, T'vec2
, T'vec3
, T'vec4
, T'mat4
, Exp
)
where
import GLSL.Internal
import Prelude hiding (id)
-- | statically typed expression; typed wrapper of a dynamic expression
-- only exposed through smart constructors
newtype Exp a = E DExp deriving Show
type E'float = Exp T'float
type E'bool = Exp T'bool
type E'vec2 = Exp T'vec2
type E'vec3 = Exp T'vec3
type E'vec4 = Exp T'vec4
type E'mat4 = Exp T'mat4
class Vec2 a where
-- | converts some input to a vec2
-- some inpu' should be a single value or a tuple of values
vec2 :: a -> E'vec2
instance Vec2 (E'float, E'float) where
vec2
( E a
, E b)
= E $ Lit'vec2 $ E'vec2_2 a b
class Vec3 a where
-- | converts some input to a vec3
-- some input should be a single value or a tuple of values
vec3 :: a -> E'vec3
instance Vec3 (E'float, E'float, E'float) where
vec3
( E a
, E b
, E c)
= E $ Lit'vec3 $ E'vec3_3 a b c
instance Vec3 (E'vec2, E'float) where
vec3
( E a
, E b)
= E $ Lit'vec3 $ E'vec3_2 a b
instance Vec3 (E'float, E'vec2) where
vec3
( E a
, E b)
= E $ Lit'vec3 $ E'vec3_2 a b
class Vec4 a where
-- | converts some input to a vec4
-- some input should be a single value or a tuple of values
vec4 :: a -> E'vec4
instance Vec4 (E'float, E'float, E'float, E'float) where
vec4
( E a
, E b
, E c
, E d)
= E $ Lit'vec4 $ E'vec4_4 a b c d
instance Vec4 (E'vec3, E'float) where
vec4
( E a
, E b)
= E $ Lit'vec4 $ E'vec4_2 a b
instance Vec4 (E'float, E'vec3) where
vec4
( E a
, E b)
= E $ Lit'vec4 $ E'vec4_2 a b
instance Vec4 (E'vec2, E'float, E'float) where
vec4
( E a
, E b
, E c)
= E $ Lit'vec4 $ E'vec4_3 a b c
instance Vec4 (E'float, E'vec2, E'float) where
vec4
( E a
, E b
, E c)
= E $ Lit'vec4 $ E'vec4_3 a b c
instance Vec4 (E'float, E'float, E'vec2) where
vec4
( E a
, E b
, E c)
= E $ Lit'vec4 $ E'vec4_3 a b c
instance Vec4 (E'vec2, E'vec2) where
vec4
( E a
, E b)
= E $ Lit'vec4 $ E'vec4_2 a b
class Normalize a b | a -> b where
-- | wraps some expression in the normalize glsl builtin
-- a is the same type as b
normalize :: a -> b
instance Normalize E'vec2 E'vec2 where
normalize (E v) = E $ E'normalize v
instance Normalize E'vec3 E'vec3 where
normalize (E v) = E $ E'normalize v
instance Normalize E'vec4 E'vec4 where
normalize (E v) = E $ E'normalize v
class Add a b c | a b -> c where
-- | puts a + between two expressions
add :: a -> b -> c
instance Add E'float E'float E'float where
add (E a) (E b) = E $ E'add a b
instance Add E'float E'vec2 E'vec2 where
add (E a) (E b) = E $ E'add a b
instance Add E'float E'vec3 E'vec3 where
add (E a) (E b) = E $ E'add a b
instance Add E'float E'vec4 E'vec4 where
add (E a) (E b) = E $ E'add a b
class Mul a b c | a b -> c where
-- | puts a * between two expressions
mul :: a -> b -> c
instance Mul E'float E'float E'float where
mul (E a) (E b) = E $ E'mul a b
instance Mul E'float E'vec2 E'vec2 where
mul (E a) (E b) = E $ E'mul a b
instance Mul E'float E'vec3 E'vec3 where
mul (E a) (E b) = E $ E'mul a b
instance Mul E'float E'vec4 E'vec4 where
mul (E a) (E b) = E $ E'mul a b
instance Mul E'mat4 E'vec4 E'vec4 where
mul (E a) (E b) = E $ E'mul a b
-- | creates a boolean literal expression
bool :: Bool -> E'bool
bool x = E $ Lit'bool x
-- | creates a typed float literal expression
float :: Float -> E'float
float x = E $ Lit'float x
-- | creates a variable with type float
var'float :: Id -> E'float
var'float name = var name T'float
-- | creates a variable with type bool
var'bool :: Id -> E'bool
var'bool name = var name T'bool
-- | creates a variable with type vec2
var'vec2 :: Id -> E'vec2
var'vec2 name = var name T'vec2
-- | creates a variable with type vec3
var'vec3 :: Id -> E'vec3
var'vec3 name = var name T'vec3
-- | creates a variable with type vec4
var'vec4 :: Id -> E'vec4
var'vec4 name = var name T'vec4
-- | creates a variable with type mat4
var'mat4 :: Id -> E'mat4
var'mat4 name = var name T'mat4
-- | creates a literal variable object to pass to mkShaders
dec_var :: Id -> Type -> Var
dec_var name t = Var name t
-- | creates an Id from a String
id :: String -> Id
id (c:cs) =
let
nondigits = ['a'..'z'] ++ ['A'..'Z'] ++ ['_']
digits = ['0'..'9']
in
if elem c nondigits && [] == filter (\e -> notElem e (digits ++ nondigits)) cs
then
Id (c:cs)
else
error $ "invalid identifier <" ++ (c:cs) ++ ">"
id "" = error "invalid blank identifier <>"
-- | creates shaders that fulfill the desired behaviour
mkShaders
:: [Var] -- ^ uniforms
-> [(Int, Var)] -- ^ vertex shader inputs
-> E'vec4 -- ^ vertex shader gl_Position expression
-> [(Var, Exp t)] -- ^ passthrough variables from vertex to fragment shader
-> Id -- ^ fragment shader output variable name
-> E'vec4 -- ^ fragment shader expression
-> (Shader, Shader) -- ^ (vert, frag)
mkShaders
uniforms
vertex_inputs
(E gl_Position_expr)
passthroughs
out_id
(E out_expr)
=
let
passthroughVars = map fst passthroughs
in
( VertexShader
Version'330_core
vertex_inputs
passthroughVars
uniforms
(E'let (Var (Id "gl_Position") T'vec4) gl_Position_expr
: map (\(ft, sd) -> E'let ft (unE sd)) passthroughs)
, FragmentShader
Version'330_core
passthroughVars
[Var out_id T'vec4]
uniforms
[E'let (Var out_id T'vec4) out_expr]
)
--------------------------------------------------------------------------------
-- internal constructors TODO remove
--------------------------------------------------------------------------------
unE :: Exp t -> DExp
unE (E a) = a
var :: Id -> Type -> Exp t
var name t = E $ E'var $ Var name t

62
src/GLSL/Compile.hs Normal file
View File

@@ -0,0 +1,62 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{- |
- Module : GLSL.Compile
- Description : lets prints
- Copyright : 2025 Andromeda
- License : BSD 3-Clause
- Maintainer : @Andromeda:tchncs.de
- Stability : Experimental
- TODO add internal module
-}
module GLSL.Compile
( uncheckedCompileShader )
where
import GLSL.Internal
import GLSL.Internal.Compile
-- | TODO indents, pretty/ugly printing
uncheckedCompileShader :: Shader -> String
uncheckedCompileShader
(FragmentShader
version
inputs
outputs
uniforms
body
)
=
(showVersion version) ++
"\n" ++
(unlines $
(map (\a -> a ++ ";") (
map showUniform uniforms
++ map showIn inputs
++ map showOut outputs
))
++ [ "void main() {" ]
++ map (\a -> showDExp a ++ ";") body
++ [ "}" ]
)
uncheckedCompileShader
(VertexShader
version
inputs
outputs
uniforms
body
)
=
showVersion version ++ "\n" ++
(unlines $
map (\a -> a ++ ";") (
map showUniform uniforms
++ map showLayoutIn inputs
++ map showOut outputs
)
++ [ "void main() {" ]
++ map (\a -> showDExp a ++ ";") body
++ [ "}" ]
)

93
src/GLSL/Internal.hs Normal file
View File

@@ -0,0 +1,93 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{- |
- Module : GLSL.Internal
- Description : internal (dynamic) representation of GLSL
- Copyright : 2025 Andromeda
- License : BSD 3-Clause
- Maintainer : @Andromeda:tchncs.de
- Stability : Experimental
-}
module GLSL.Internal where
-- | literal shaders
data Shader
= VertexShader
Version -- ^ version
[(Int, Var)] -- ^ inputs in form (layoutIndex, input)
[Var] -- ^ outputs
[Var] -- ^ uniforms
[DExp] -- ^ body
| FragmentShader
Version -- ^ version
[Var] -- ^ inputs
[Var] -- ^ outputs
[Var] -- ^ uniforms
[DExp] -- ^ body
deriving Show
-- | version adt
data Version
= Version'330_core
deriving Show
-- | dynamically typed expressions
data DExp
= Lit'float Float
| Lit'bool Bool
| Lit'vec2 DExp
| Lit'vec3 DExp
| Lit'vec4 DExp
| E'var Var
| E'let Var DExp
| E'if DExp DExp DExp
| E'add DExp DExp
| E'mul DExp DExp
| E'normalize DExp
-- TODO move these somewhere else? maybe
| E'vec2_2 DExp DExp
| E'vec3_2 DExp DExp
| E'vec3_3 DExp DExp DExp
| E'vec4_2 DExp DExp
| E'vec4_3 DExp DExp DExp
| E'vec4_4 DExp DExp DExp DExp
deriving Show
-- | stores variable declaration
data Var
= Var Id Type
deriving Show
-- | GLSL types
data Type
= T'bool
| T'float
| T'vec2
| T'vec3
| T'vec4
| T'mat4
deriving Show
-- | float
data T'float
-- | bool
data T'bool
-- | vec2
data T'vec2
-- | vec3
data T'vec3
-- | vec4
data T'vec4
-- | mat4
data T'mat4
-- | identifier for variables
newtype Id = Id String deriving Show

View File

@@ -0,0 +1,72 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{- |
- Module : GLSL.Internal.Compile
- Description : prints
- Copyright : 2025 Andromeda
- License : BSD 3-Clause
- Maintainer : @Andromeda:tchncs.de
- Stability : Experimental
-}
module GLSL.Internal.Compile where
import GLSL.Internal
-- | prints version as version header
showVersion :: Version -> String
showVersion Version'330_core = "#version 330 core"
-- | prints variable with uniform qualifier
showUniform :: Var -> String
showUniform var = "uniform " ++ showVar var
-- | prints variable with in qualifier
showIn :: Var -> String
showIn var = "in " ++ showVar var
-- | prints variable with layout (location = <>) in qualifier
showLayoutIn :: (Int, Var) -> String
showLayoutIn (i, var) = "layout (location = " ++ show i ++ ") in " ++ showVar var
-- | prints variable with out qualifier
showOut :: Var -> String
showOut var = "out " ++ showVar var
-- | prints variable declaration <typ> <name>
showVar :: Var -> String
showVar (Var name typ) = showType typ ++ " " ++ showId name
-- | prints a dynamic expression
showDExp :: DExp -> String
showDExp (Lit'float f) = show f
showDExp (E'var (Var name _)) = showId name
showDExp (E'let (Var name _) exp0) = showId name ++ " = " ++ showDExp exp0
showDExp (E'if cond exp0 exp1) = "if " ++ showDExp cond ++ "\nthen\n" ++ showDExp exp0 ++ "\nelse\n" ++ showDExp exp1
showDExp (E'add exp0 exp1) = showDExp exp0 ++ " + " ++ showDExp exp1
showDExp (E'mul exp0 exp1) = showDExp exp0 ++ " * " ++ showDExp exp1
showDExp (E'normalize exp0) = "normalize(" ++ showDExp exp0 ++ ")"
showDExp (Lit'bool cond) = if cond then "true" else "false"
showDExp (Lit'vec2 exp0) = showDExp exp0
showDExp (Lit'vec3 exp0) = showDExp exp0
showDExp (Lit'vec4 exp0) = showDExp exp0
showDExp (E'vec2_2 f0 f1) = "vec2(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ")"
showDExp (E'vec3_2 f0 f1) = "vec3(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ")"
showDExp (E'vec4_2 f0 f1) = "vec4(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ")"
showDExp (E'vec3_3 f0 f1 f2) = "vec3(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ", " ++ showDExp f2 ++ ")"
showDExp (E'vec4_3 f0 f1 f2) = "vec4(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ", " ++ showDExp f2 ++ ")"
showDExp (E'vec4_4 f0 f1 f2 f3) = "vec4(" ++ showDExp f0 ++ ", " ++ showDExp f1 ++ ", " ++ showDExp f2 ++ ", " ++ showDExp f3 ++ ")"
-- | toString for Type
showType :: Type -> String
showType T'bool = "bool"
showType T'float = "float"
showType T'vec2 = "vec2"
showType T'vec3 = "vec3"
showType T'vec4 = "vec4"
showType T'mat4 = "mat4"
-- | prints verbatim identifier
showId :: Id -> String
showId (Id s) = s

View File

@@ -1,271 +0,0 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Language.GLSL
( Program
, Expressions
, Expression(..)
, RequestedGLVersion
, RequestedGLType(..)
, ParameterQualifier(..)
, LayoutQualifier(..)
, Variables
, VariableName
, Variable(..)
, GLSLType(..)
, generateGLSL
, generateCheckedGLSL
)
where
-- TODO
--
-- -[ ] Add support for multiple shaders in sequence
-- -[ ] Add check if the previous shader output the next shader's inputs
-- IMPORTS --
import Relude
-- CLASSES --
class GLSLExpression a where
-- should display the token that the type represents
toGLSLText :: a -> Text
instance GLSLExpression Program where
toGLSLText expressions =
foldr ((<>)) "}\n" $ map (toGLSLText) expressions
instance GLSLExpression Expression where
toGLSLText
(VersionDeclaration requestedGLVersion requestedGLType) =
"#version " <>
(toGLSLText requestedGLVersion) <>
" " <>
(toGLSLText requestedGLType) <>
"\n"
toGLSLText
(VariableDeclaration
(Just (Location location))
parameterQualifier
(Variable variableName glslType)) =
"layout (location = " <>
(toGLSLText location) <>
") " <>
(toGLSLText parameterQualifier) <>
" " <>
(toGLSLText glslType) <>
" " <>
variableName <>
";\n"
toGLSLText
(VariableDeclaration
Nothing
parameterQualifier
(Variable variableName glslType)) =
(toGLSLText parameterQualifier) <>
" " <>
(toGLSLText glslType) <>
" " <>
variableName <>
";\n"
toGLSLText
MainStart =
"void main()\n{\n"
toGLSLText
(VariableAssignment
variable0
variable1) =
(toGLSLText variable0) <>
" = " <>
(toGLSLText variable1) <>
";\n"
toGLSLText
(DangerousExpression text) =
text <>
"\n"
instance GLSLExpression RequestedGLVersion where
toGLSLText i = show i
instance GLSLExpression RequestedGLType where
toGLSLText Core = "core"
instance GLSLExpression ParameterQualifier where
toGLSLText In = "in"
toGLSLText Out = "out"
toGLSLText Uniform = "uniform"
instance GLSLExpression Variable where
toGLSLText GL_POSITION = "gl_Position"
toGLSLText (Variable name _) = name
instance GLSLExpression GLSLType where
toGLSLText (GLSLVec4 _) = "vec4"
toGLSLText (GLSLVec3 _) = "vec3"
toGLSLText (GLSLVec2 _) = "vec2"
toGLSLText (GLSLMat4 _) = "mat4"
toGLSLText GLSLFloat = "float"
-- TYPES --
type Program = Expressions
-- version declaration
type RequestedGLVersion = Int
data RequestedGLType
= Core
deriving Show
-- variables
data ParameterQualifier
= In
| Out
| Uniform
deriving (Eq, Show)
data LayoutQualifier
= Location Int
deriving Show
type Variables = [Variable]
type VariableName = Text
data Variable
= Variable
VariableName
GLSLType
| GL_POSITION
deriving (Eq, Show)
data GLSLType
= GLSLFloat
| GLSLVec4 GLSLType
| GLSLVec3 GLSLType
| GLSLVec2 GLSLType
| GLSLMat4 GLSLType
deriving (Eq, Show)
-- expressions
type Expressions = [Expression]
data Expression
= VersionDeclaration
RequestedGLVersion
RequestedGLType
| VariableDeclaration
(Maybe LayoutQualifier)
ParameterQualifier
Variable
| MainStart
| VariableAssignment
Variable
Variable
| DangerousExpression
Text
deriving Show
-- errors
type Check = CheckSuccess -> CheckResult
type CheckResult = Either CheckFailure CheckSuccess
type CheckSuccess = (Program, [Warn])
type CheckFailure = (Error, [Warn])
data Error
= ErrUnimplementedCheckProgram
| ErrVariableDeclaredMultipleTimes Variables
| ErrVariableOutputUnassigned Variables
deriving Show
data Warn
= WarnVariableAssignedMultipleTimes Variable Int
| WarnVariableUnassigned Variable
deriving Show
generateGLSL :: Program -> Text
generateGLSL = toGLSLText
generateCheckedGLSL :: Program -> Either CheckFailure (Text, [Warn])
generateCheckedGLSL program =
case checkProgram (program, []) of
Left (e, warnings) -> Left (e, warnings)
Right (program, warnings) -> Right (toGLSLText program, warnings)
checkProgram :: Check
checkProgram program
= checkVariableDeclaredMultipleTimes program
>>= checkVariableOutputUnassigned
>>= checkVariableAssignedMultipleTimes
>>= checkVariableUnassigned
-- throws error if variable declared multiple times
checkVariableDeclaredMultipleTimes :: Check
checkVariableDeclaredMultipleTimes (program, warnings)
= case [var | (var, ct) <- counts [v | VariableDeclaration _ _ v <- program]
, ct > 1
] of
[] -> Right (program, warnings)
a -> Left (ErrVariableDeclaredMultipleTimes a, warnings)
-- throws error if `out` variable unassigned
checkVariableOutputUnassigned :: Check
checkVariableOutputUnassigned (program, warnings)
= case
[v | v <- [var | VariableDeclaration _ Out var <- program]
, not $ elem v [var | VariableAssignment var _ <- program]
] of
[] -> Right (program, warnings)
a -> Left (ErrVariableOutputUnassigned a, warnings)
-- throws warning if variable assigned multiple times
checkVariableAssignedMultipleTimes :: Check
checkVariableAssignedMultipleTimes (program, warnings)
= case [(var, ct) | (var, ct) <- counts [v | VariableAssignment v _ <- program]
, ct > 1
] of
[] -> Right (program, warnings)
a -> Right
(program
, (map
(\(var, ct) -> WarnVariableAssignedMultipleTimes var ct)
a) ++ warnings
)
-- throws warning if non-input variable unassigned
checkVariableUnassigned :: Check
checkVariableUnassigned (program, warnings)
= case
[WarnVariableUnassigned var | VariableDeclaration _ parameterQualifier var <- program
, parameterQualifier /= In
, not $ elem var [var | VariableAssignment var _ <- program]
] of
[] -> Right (program, warnings)
a -> Right (program, a ++ warnings)
-- utilities
counts :: (Eq a) => [a] -> [(a, Int)]
counts arr = counts' arr []
counts' :: (Eq a) => [a] -> [(a, Int)] -> [(a, Int)]
counts' [] cts = cts
counts' (x:xs) cts =
counts'
xs
$ if [] == [0 | (a, _) <- cts
, a == x
]
then
(x, 1):cts
else
[(a, ct + 1) | (a, ct) <- cts
, a == x
] ++
[t | t@(a, _) <- cts
, a /= x
]

View File

@@ -1,47 +0,0 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Language.GLSL.Examples (main) where
-- IMPORTS --
import Text.Pretty.Simple
import Relude
import Language.GLSL
-- MAIN --
main :: IO ()
main = do
let a = generateCheckedGLSL fragShader
let b = generateCheckedGLSL vertShader
pPrint a
pPrint b
return ()
fragShader :: Program
fragShader =
[ VersionDeclaration 450 Core
, VariableDeclaration Nothing In fragColorOut
, VariableDeclaration Nothing Out fragColor
, MainStart
, VariableAssignment fragColor fragColorOut
]
vertShader :: Program
vertShader =
[ VersionDeclaration 450 Core
, VariableDeclaration (Just $ Location 0) In vertexPosition
, VariableDeclaration (Just $ Location 1) In vertexColor
, VariableDeclaration Nothing Out fragColorOut
, MainStart
, VariableAssignment GL_POSITION vertexPosition
, VariableAssignment fragColorOut vertexColor
]
fragColor = Variable "fragColor" $ GLSLVec4 GLSLFloat
fragColorOut = Variable "fragColorOut" $ GLSLVec4 GLSLFloat
vertexPosition = Variable "vertexPosition" $ GLSLVec4 GLSLFloat
vertexColor = Variable "vertexColor" $ GLSLVec4 GLSLFloat

50
src/Main.hs Normal file
View File

@@ -0,0 +1,50 @@
module Main (main) where
import Prelude hiding (id)
import GLSL
import GLSL.Compile
main :: IO ()
main = do
let
-- variables
a_vPos = var'vec3 $ id "a_vPos"
v_pos = var'vec3 $ id "v_pos"
u_view = var'mat4 $ id "u_view"
u_projection = var'mat4 $ id "u_projection"
-- variable declarations
a_vPos' = dec_var (id "a_vPos") T'vec3
v_pos' = dec_var (id "v_pos") T'vec3
u_view' = dec_var (id "u_view") T'mat4
u_projection' = dec_var (id "u_projection") T'mat4
-- shaders
(vert, frag) = mkShaders
-- uniforms
[ u_view'
, u_projection']
-- vertex inputs
[ (0, a_vPos')]
-- gl_Position expression
(
u_projection `mul` (u_view `mul` vec4 (v_pos, float 1))
)
-- passthrough
[ (v_pos', a_vPos) ]
-- output name
(id "o_vColor")
-- output expression
(
vec4
( float 0.5 `add`
float 0.5 `mul`
normalize v_pos :: Exp T'vec3
, float 1
)
)
putStrLn $ show vert
putStrLn $ show frag
putStrLn $ uncheckedCompileShader vert
putStrLn $ uncheckedCompileShader frag