License | GPL-2.0-or-later AND BSD-3-Clause |
---|---|
Safe Haskell | Safe |
Language | Haskell2010 |
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
idfromValue
=genericFromValue
id instanceZinza
License wheretoType
=genericToTypeSFP
toValue
=genericToValueSFP
fromValue
=genericFromValueSFP
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, ($), not, return) 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 few constructions:
- field access
foo.bar
- function application
fun bar
(though function can only benot
)
Note: you can provide your own Prelude of functions. See Bools.hs
and Bools.zinza
in tests for an example.
You cannot define new functions in templates, but you can pass
them as template arguments.
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.
Blocks
It's possible to define blocks to be used (possibly multiple times) later:
{% defblock blockname %} ... {% endblock %}
And the block can be used later with:
{% useblock blockname %}
Blocks follow scopes of if
and for
control structures
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
- fromValue :: Loc -> Value -> Either RuntimeError a
- fromValueList :: Loc -> Value -> Either RuntimeError [a]
- 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
- genericFromValue :: forall a. (Generic a, GZinzaFrom (Rep a)) => (String -> String) -> Loc -> Value -> Either RuntimeError a
- 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
- genericFromValueSFP :: forall a. (Generic a, GZinzaFrom (Rep a), GFieldNames (Rep a)) => Loc -> Value -> Either RuntimeError a
- stripFieldPrefix :: forall a. (Generic a, GFieldNames (Rep a)) => Proxy a -> String -> String
- class GZinzaType (f :: Type -> Type)
- class GZinzaValue (f :: Type -> Type)
- class GZinzaFrom (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; fromValue = genericFromValueSFP
>>>
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 #
fromValue :: Loc -> Value -> Either RuntimeError a Source #
fromValueList :: Loc -> Value -> Either RuntimeError [a] Source #
Instances
Generic deriving
Generically derive toType
function.
Generically derive toValue
function.
:: (Generic a, GZinzaFrom (Rep a)) | |
=> (String -> String) | field renamer |
-> Loc | |
-> Value | |
-> Either RuntimeError a |
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 #
genericFromValueSFP :: forall a. (Generic a, GZinzaFrom (Rep a), GFieldNames (Rep a)) => Loc -> Value -> Either RuntimeError a 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 GZinzaFrom (f :: Type -> Type) Source #
gfromValue
Instances
(i ~ D, GZinzaFromSum f) => GZinzaFrom (M1 i c f) Source # | |
Defined in Zinza.Generic gfromValue :: Loc -> Ty -> (Var -> Maybe Value) -> Either RuntimeError (M1 i c f ()) |
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, |
NDefBlock Loc Var (Nodes a) | define block |
NUseBlock Loc Var | use block |
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.
EVar (Located a) | variable |
EField (LExpr a) (Located Var) | field accessor |
EApp (LExpr a) (LExpr a) | function application |
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 |
TyFun Ty Ty | functions |
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 #
UnboundTopLevelVar Loc Var | |
ShadowingBlock Loc Var | |
UnboundUseBlock Loc Var | |
ARuntimeError RuntimeError |
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 #
NotBool Loc Ty | |
NotString Loc Ty | |
NotRecord Loc Ty | |
NotList Loc Ty | |
FieldNotInRecord Loc Var Ty | |
NotFunction Loc Ty | |
FunArgDontMatch Loc Ty Ty | |
CustomError Loc String Ty |
Instances
Eq RuntimeError Source # | |
Defined in Zinza.Errors (==) :: RuntimeError -> RuntimeError -> Bool # (/=) :: RuntimeError -> RuntimeError -> Bool # | |
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 # | |
ThrowRuntime m => ThrowRuntime (StateT s m) Source # | |
Defined in Zinza.Errors throwRuntime :: RuntimeError -> StateT s m 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 # |