Safe Haskell | Safe |
---|---|
Language | Haskell2010 |
SPDX-Identifier-Id: GPL-2.0-or-later AND BSD-3-Clause
Zinza - a small jinja-syntax-inspired typed-template compiler.
Zinza typechecks and compiles a template. We can compile either to Haskell function, or to verbatim Haskell module (planned).
Zinza is very minimalistic. Features are added when needed.
Example usage
Given a template
{% for license in licenses %} licenseName {{license.con}} = {{license.name}} {% endfor %}
and data definitions like:
newtype Licenses = Licenses { licenses :: [License] } deriving (Generic) data License = License { licenseCon :: String , licenseName :: String } deriving (Generic)
We can (generically) derive Zinza
instances for Licenses
and License
instanceZinza
Licenses wheretoType
=genericToType
idtoValue
=genericToValue
id instanceZinza
License wheretoType
=genericToTypeSFP
toValue
=genericToValueSFP
Then the example of run-time usage is
example :: IO String
example = do
-- this might fail, type errors!
run <- parseAndCompileTemplateIO
"fixtures/licenses.zinza"
-- this shouldn't fail (run-time errors are due bugs in zinza)
run $ Licenses
[ License "Foo" (show "foo-1.0")
, License "Bar" (show "bar-1.2")
]
The result of running an example
is:
licenseName Foo = "foo-1.0" licenseName Bar = "bar-1.2"
Module generation
Zinza also supports standalone module generation.
parseAndCompileModuleIO
(simpleConfig
"DemoLicenses" ["Licenses"] ::ModuleConfig
Licenses) "fixtures/licenses.zinza" >>= putStr
prints a Haskell module source code:
module DemoLicenses (render) where import Prelude (String, fst, snd, ($)) import Control.Monad (forM_) import Licenses type Writer a = (String, a) tell :: String -> Writer (); tell x = (x, ()) execWriter :: Writer a -> String; execWriter = fst render :: Licenses -> String render (z_root) = execWriter $ do forM_ (licenses $ z_root) $ z_var0_license -> do tell "licenseName " tell (licenseCon $ z_var0_license) tell " = " tell (licenseName $ z_var0_license) tell "n"
which is not dependent on Zinza. You are free to use more efficient writer as well.
Expressions
{{ expression }}
Expression syntax has only two structures
- negated:
!foo
- field access
foo.bar
Control structures
The for and if statements are supported:
{% for value in values %} ... {% endfor %}
{% if boolExpression %} ... {% elif anotherBoolExpression %} ... {% else %} ... {% endif %}
If a control structure tag starts at the first column, the possible trailing new line feed is stripped. This way full-line control tags don't introduce new lines in the output.
Comments
{# Comments are omitted from the output #}
Synopsis
- parseAndCompileTemplate :: (Zinza a, ThrowRuntime m) => FilePath -> String -> Either CompileOrParseError (a -> m String)
- parseAndCompileTemplateIO :: (Zinza a, ThrowRuntime m) => FilePath -> IO (a -> m String)
- parseAndCompileModule :: Zinza a => ModuleConfig a -> FilePath -> String -> Either CompileOrParseError String
- parseAndCompileModuleIO :: Zinza a => ModuleConfig a -> FilePath -> IO String
- data ModuleConfig a = ModuleConfig {}
- simpleConfig :: forall a. Typeable a => String -> [String] -> ModuleConfig a
- class Zinza a where
- toType :: Proxy a -> Ty
- toTypeList :: Proxy a -> Ty
- toValue :: a -> Value
- toValueList :: [a] -> Value
- genericToType :: forall a. (Generic a, GZinzaType (Rep a)) => (String -> String) -> Proxy a -> Ty
- genericToValue :: forall a. (Generic a, GZinzaValue (Rep a)) => (String -> String) -> a -> Value
- genericToTypeSFP :: forall a. (Generic a, GZinzaType (Rep a), GFieldNames (Rep a)) => Proxy a -> Ty
- genericToValueSFP :: forall a. (Generic a, GZinzaValue (Rep a), GFieldNames (Rep a)) => a -> Value
- stripFieldPrefix :: forall a. (Generic a, GFieldNames (Rep a)) => Proxy a -> String -> String
- class GZinzaType (f :: Type -> Type)
- class GZinzaValue (f :: Type -> Type)
- class GFieldNames (f :: Type -> Type)
- data Node a
- type Nodes a = [Node a]
- data Expr a
- type LExpr a = Located (Expr a)
- data Ty
- displayTy :: Ty -> String
- data Value
- newtype ParseError = ParseError String
- data CompileError
- data CompileOrParseError
- data RuntimeError
- class AsRuntimeError e where
- asRuntimeError :: RuntimeError -> e
- class Monad m => ThrowRuntime m where
- throwRuntime :: RuntimeError -> m a
- data Loc = Loc !Int !Int
- data Located a = L !Loc a
- zeroLoc :: Loc
- displayLoc :: Loc -> String
- class Traversable t => TraversableWithLoc t where
- traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> t a -> f (t b)
- type Var = String
- type Selector = String
Documentation
parseAndCompileTemplate Source #
:: (Zinza a, ThrowRuntime m) | |
=> FilePath | name of the template |
-> String | contents of the template |
-> Either CompileOrParseError (a -> m String) |
Parse and compile the template into Haskell function.
parseAndCompileTemplateIO :: (Zinza a, ThrowRuntime m) => FilePath -> IO (a -> m String) Source #
Like parseAndCompileTemplate
but reads file and (possibly)
throws CompileOrParseError
.
Compilation to Haskell module
parseAndCompileModule :: Zinza a => ModuleConfig a -> FilePath -> String -> Either CompileOrParseError String Source #
Parse and compile the template into String
representing a Haskell module.
parseAndCompileModuleIO :: Zinza a => ModuleConfig a -> FilePath -> IO String Source #
Like parseAndCompileModule
but reads file and (possibly)
throws CompileOrParseError
.
data ModuleConfig a Source #
Configuration for module rendering
Instances
Show (ModuleConfig a) Source # | |
Defined in Zinza.Module showsPrec :: Int -> ModuleConfig a -> ShowS # show :: ModuleConfig a -> String # showList :: [ModuleConfig a] -> ShowS # |
:: Typeable a | |
=> String | module name |
-> [String] | imports |
-> ModuleConfig a |
Simple configuration to use with parseAndCompileModule
or
parseAndCompileModuleIO
.
Input class
Zinza
class tells how to convert the type into template parameters,
and their types.
Class can be auto-derived for product types.
>>>
data R = R { recFoo :: String, recBar :: Char } deriving Generic
>>>
instance Zinza R where toType = genericToTypeSFP; toValue = genericToValueSFP
>>>
displayTy $ toType (Proxy :: Proxy R)
"{bar: String, foo: String}"
toType :: Proxy a -> Ty Source #
toTypeList :: Proxy a -> Ty Source #
toValue :: a -> Value Source #
toValueList :: [a] -> Value Source #
Instances
Zinza Bool Source # | |
Zinza Char Source # | |
Zinza () Source # | |
Zinza Text Source # | |
Zinza Text Source # | |
Zinza a => Zinza [a] Source # | |
Zinza a => Zinza (NonEmpty a) Source # | |
Zinza a => Zinza (Set a) Source # | |
(Zinza a, Zinza b) => Zinza (a, b) Source # | |
(Zinza k, Zinza v) => Zinza (Map k v) Source # | Pairs are encoded as |
Generic deriving
Generically derive toType
function.
Generically derive toValue
function.
genericToTypeSFP :: forall a. (Generic a, GZinzaType (Rep a), GFieldNames (Rep a)) => Proxy a -> Ty Source #
genericToValueSFP :: forall a. (Generic a, GZinzaValue (Rep a), GFieldNames (Rep a)) => a -> Value Source #
stripFieldPrefix :: forall a. (Generic a, GFieldNames (Rep a)) => Proxy a -> String -> String Source #
Field renamer which will automatically strip lowercase prefix from field names.
>>>
data R = R { recFoo :: Int, recBar :: Char } deriving Generic
>>>
stripFieldPrefix (Proxy :: Proxy R) "recFoo"
"foo"
If whole field is lower case, it's left intact
>>>
newtype Wrapped = Wrap { unwrap :: String } deriving Generic
>>>
stripFieldPrefix (Proxy :: Proxy Wrapped) "unwrap"
"unwrap"
class GZinzaType (f :: Type -> Type) Source #
gtoType
class GZinzaValue (f :: Type -> Type) Source #
gtoValue
Instances
(i ~ D, GZinzaValueSum f) => GZinzaValue (M1 i c f) Source # | |
Defined in Zinza.Generic |
class GFieldNames (f :: Type -> Type) Source #
fieldNames
Instances
(i ~ D, GFieldNamesSum f) => GFieldNames (M1 i c f) Source # | |
Defined in Zinza.Generic fieldNames :: Proxy (M1 i c f) -> [String] |
Templates
Template parts.
We use polymorphic recursion for de Bruijn indices.
See materials on bound
library.
NRaw String | raw text block |
NExpr (LExpr a) | expression |
NIf (LExpr a) (Nodes a) (Nodes a) | conditional block, |
NFor Var (LExpr a) (Nodes (Maybe a)) | for loop, |
NComment | comments |
Instances
Functor Node Source # | |
Foldable Node Source # | |
Defined in Zinza.Node fold :: Monoid m => Node m -> m # foldMap :: Monoid m => (a -> m) -> Node a -> m # foldr :: (a -> b -> b) -> b -> Node a -> b # foldr' :: (a -> b -> b) -> b -> Node a -> b # foldl :: (b -> a -> b) -> b -> Node a -> b # foldl' :: (b -> a -> b) -> b -> Node a -> b # foldr1 :: (a -> a -> a) -> Node a -> a # foldl1 :: (a -> a -> a) -> Node a -> a # elem :: Eq a => a -> Node a -> Bool # maximum :: Ord a => Node a -> a # | |
Traversable Node Source # | |
TraversableWithLoc Node Source # | |
Defined in Zinza.Node traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Node a -> f (Node b) Source # | |
Show a => Show (Node a) Source # | |
Expressions in templates.
Note: there are only eliminators; we cannot construct "bigger" expressions.
Instances
Monad Expr Source # |
|
Functor Expr Source # | |
Applicative Expr Source # | |
Foldable Expr Source # | |
Defined in Zinza.Expr fold :: Monoid m => Expr m -> m # foldMap :: Monoid m => (a -> m) -> Expr a -> m # foldr :: (a -> b -> b) -> b -> Expr a -> b # foldr' :: (a -> b -> b) -> b -> Expr a -> b # foldl :: (b -> a -> b) -> b -> Expr a -> b # foldl' :: (b -> a -> b) -> b -> Expr a -> b # foldr1 :: (a -> a -> a) -> Expr a -> a # foldl1 :: (a -> a -> a) -> Expr a -> a # elem :: Eq a => a -> Expr a -> Bool # maximum :: Ord a => Expr a -> a # | |
Traversable Expr Source # | |
TraversableWithLoc Expr Source # | |
Defined in Zinza.Expr traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Expr a -> f (Expr b) Source # | |
Show a => Show (Expr a) Source # | |
Types
Zinza's type-system is delibarately extremely simple.
Zinza types.
The selector
s tell how the Haskell value can be
converted to primitive value. E.g.
>>>
toType (Proxy :: Proxy Char)
TyString (Just "return")
TyBool | boolean |
TyString (Maybe Selector) | string |
TyList (Maybe Selector) Ty | lists |
TyRecord (Map Var (Selector, Ty)) | records |
Values
Value
s are passed at run-time, when the template is interpreted.
When compiled to the Haskell module, Value
s aren't used.
Template values.
Errors
newtype ParseError Source #
Instances
Show ParseError Source # | |
Defined in Zinza.Errors showsPrec :: Int -> ParseError -> ShowS # show :: ParseError -> String # showList :: [ParseError] -> ShowS # | |
Exception ParseError Source # | |
Defined in Zinza.Errors toException :: ParseError -> SomeException # fromException :: SomeException -> Maybe ParseError # displayException :: ParseError -> String # |
data CompileError Source #
Instances
Show CompileError Source # | |
Defined in Zinza.Errors showsPrec :: Int -> CompileError -> ShowS # show :: CompileError -> String # showList :: [CompileError] -> ShowS # | |
Exception CompileError Source # | |
Defined in Zinza.Errors | |
AsRuntimeError CompileError Source # | |
Defined in Zinza.Errors |
data CompileOrParseError Source #
Instances
Show CompileOrParseError Source # | |
Defined in Zinza.Errors showsPrec :: Int -> CompileOrParseError -> ShowS # show :: CompileOrParseError -> String # showList :: [CompileOrParseError] -> ShowS # | |
Exception CompileOrParseError Source # | |
Defined in Zinza.Errors |
data RuntimeError Source #
Instances
Show RuntimeError Source # | |
Defined in Zinza.Errors showsPrec :: Int -> RuntimeError -> ShowS # show :: RuntimeError -> String # showList :: [RuntimeError] -> ShowS # | |
Exception RuntimeError Source # | |
Defined in Zinza.Errors | |
AsRuntimeError RuntimeError Source # | |
Defined in Zinza.Errors |
class AsRuntimeError e where Source #
Class representing errors containing RuntimeError
s.
Without bugs, compiled template should not throw any RuntimeError
s,
as they are prevented statically, i.e. reported already as CompileError
s.
asRuntimeError :: RuntimeError -> e Source #
Instances
AsRuntimeError RuntimeError Source # | |
Defined in Zinza.Errors | |
AsRuntimeError CompileError Source # | |
Defined in Zinza.Errors |
class Monad m => ThrowRuntime m where Source #
throwRuntime :: RuntimeError -> m a Source #
Instances
ThrowRuntime IO Source # | |
Defined in Zinza.Errors throwRuntime :: RuntimeError -> IO a Source # | |
AsRuntimeError e => ThrowRuntime (Either e) Source # | |
Defined in Zinza.Errors throwRuntime :: RuntimeError -> Either e a Source # |
Location
Location, line and column.
Located element.
Instances
Functor Located Source # | |
Foldable Located Source # | |
Defined in Zinza.Pos fold :: Monoid m => Located m -> m # foldMap :: Monoid m => (a -> m) -> Located a -> m # foldr :: (a -> b -> b) -> b -> Located a -> b # foldr' :: (a -> b -> b) -> b -> Located a -> b # foldl :: (b -> a -> b) -> b -> Located a -> b # foldl' :: (b -> a -> b) -> b -> Located a -> b # foldr1 :: (a -> a -> a) -> Located a -> a # foldl1 :: (a -> a -> a) -> Located a -> a # elem :: Eq a => a -> Located a -> Bool # maximum :: Ord a => Located a -> a # minimum :: Ord a => Located a -> a # | |
Traversable Located Source # | |
Eq a => Eq (Located a) Source # | |
Show a => Show (Located a) Source # | |
displayLoc :: Loc -> String Source #
Pretty-print location.
class Traversable t => TraversableWithLoc t where Source #
Some containers have location for each element.
traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> t a -> f (t b) Source #
Instances
TraversableWithLoc Expr Source # | |
Defined in Zinza.Expr traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Expr a -> f (Expr b) Source # | |
TraversableWithLoc Node Source # | |
Defined in Zinza.Node traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Node a -> f (Node b) Source # |