zinza-0.2: Typed templates with jinja like syntax

LicenseGPL-2.0-or-later AND BSD-3-Clause
Safe HaskellSafe
LanguageHaskell2010

Zinza

Contents

Description

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

instance Zinza Licenses where
    toType    = genericToType    id
    toValue   = genericToValue   id
    fromValue = genericFromValue id

instance Zinza License where
    toType    = 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 be not)

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

Documentation

parseAndCompileTemplate Source #

Arguments

:: (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.

data ModuleConfig a Source #

Configuration for module rendering

Constructors

ModuleConfig 

Fields

Instances
Show (ModuleConfig a) Source # 
Instance details

Defined in Zinza.Module

simpleConfig Source #

Arguments

:: Typeable a 
=> String

module name

-> [String]

imports

-> ModuleConfig a 

Simple configuration to use with parseAndCompileModule or parseAndCompileModuleIO.

Input class

class Zinza a where Source #

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}"

Minimal complete definition

toType, toValue, fromValue

Instances
Zinza Bool Source # 
Instance details

Defined in Zinza.Class

Zinza Char Source # 
Instance details

Defined in Zinza.Class

Zinza () Source # 
Instance details

Defined in Zinza.Class

Zinza Text Source # 
Instance details

Defined in Zinza.Class

Zinza Text Source # 
Instance details

Defined in Zinza.Class

Zinza a => Zinza [a] Source # 
Instance details

Defined in Zinza.Class

Zinza a => Zinza (NonEmpty a) Source # 
Instance details

Defined in Zinza.Class

(Zinza a, Ord a) => Zinza (Set a) Source # 
Instance details

Defined in Zinza.Class

(Zinza a, Zinza b) => Zinza (a -> b) Source #

The fromValue for function produces partial functions. Use with care.

This means that higher order functions in templates might throw pure exception. They wont, if they are well-typed.

Instance details

Defined in Zinza.Class

Methods

toType :: Proxy (a -> b) -> Ty Source #

toTypeList :: Proxy (a -> b) -> Ty Source #

toValue :: (a -> b) -> Value Source #

toValueList :: [a -> b] -> Value Source #

fromValue :: Loc -> Value -> Either RuntimeError (a -> b) Source #

fromValueList :: Loc -> Value -> Either RuntimeError [a -> b] Source #

(Zinza a, Zinza b) => Zinza (a, b) Source # 
Instance details

Defined in Zinza.Class

Methods

toType :: Proxy (a, b) -> Ty Source #

toTypeList :: Proxy (a, b) -> Ty Source #

toValue :: (a, b) -> Value Source #

toValueList :: [(a, b)] -> Value Source #

fromValue :: Loc -> Value -> Either RuntimeError (a, b) Source #

fromValueList :: Loc -> Value -> Either RuntimeError [(a, b)] Source #

(Zinza k, Zinza v, Ord k) => Zinza (Map k v) Source #

Pairs are encoded as { key: k, val: v }

Instance details

Defined in Zinza.Class

Generic deriving

genericToType Source #

Arguments

:: (Generic a, GZinzaType (Rep a)) 
=> (String -> String)

field renamer

-> Proxy a 
-> Ty 

Generically derive toType function.

genericToValue Source #

Arguments

:: (Generic a, GZinzaValue (Rep a)) 
=> (String -> String)

field renamer

-> a 
-> Value 

Generically derive toValue function.

genericFromValue Source #

Arguments

:: (Generic a, GZinzaFrom (Rep a)) 
=> (String -> String)

field renamer

-> Loc 
-> Value 
-> Either RuntimeError a 

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 #

Minimal complete definition

gtoType

Instances
(i ~ D, GZinzaTypeSum f) => GZinzaType (M1 i c f) Source # 
Instance details

Defined in Zinza.Generic

Methods

gtoType :: Proxy (M1 i c f) -> [(String, Ty)]

class GZinzaValue (f :: Type -> Type) Source #

Minimal complete definition

gtoValue

Instances
(i ~ D, GZinzaValueSum f) => GZinzaValue (M1 i c f) Source # 
Instance details

Defined in Zinza.Generic

Methods

gtoValue :: M1 i c f () -> [(Var, Value)]

class GZinzaFrom (f :: Type -> Type) Source #

Minimal complete definition

gfromValue

Instances
(i ~ D, GZinzaFromSum f) => GZinzaFrom (M1 i c f) Source # 
Instance details

Defined in Zinza.Generic

Methods

gfromValue :: Loc -> Ty -> (Var -> Maybe Value) -> Either RuntimeError (M1 i c f ())

class GFieldNames (f :: Type -> Type) Source #

Minimal complete definition

fieldNames

Instances
(i ~ D, GFieldNamesSum f) => GFieldNames (M1 i c f) Source # 
Instance details

Defined in Zinza.Generic

Methods

fieldNames :: Proxy (M1 i c f) -> [String]

Templates

data Node a Source #

Template parts.

We use polymorphic recursion for de Bruijn indices. See materials on bound library.

Constructors

NRaw String

raw text block

NExpr (LExpr a)

expression expr : String

NIf (LExpr a) (Nodes a) (Nodes a)

conditional block, expr : Bool

NFor Var (LExpr a) (Nodes (Maybe a))

for loop, expr : List a

NDefBlock Loc Var (Nodes a)

define block

NUseBlock Loc Var

use block

NComment

comments

Instances
Functor Node Source # 
Instance details

Defined in Zinza.Node

Methods

fmap :: (a -> b) -> Node a -> Node b #

(<$) :: a -> Node b -> Node a #

Foldable Node Source # 
Instance details

Defined in Zinza.Node

Methods

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 #

toList :: Node a -> [a] #

null :: Node a -> Bool #

length :: Node a -> Int #

elem :: Eq a => a -> Node a -> Bool #

maximum :: Ord a => Node a -> a #

minimum :: Ord a => Node a -> a #

sum :: Num a => Node a -> a #

product :: Num a => Node a -> a #

Traversable Node Source # 
Instance details

Defined in Zinza.Node

Methods

traverse :: Applicative f => (a -> f b) -> Node a -> f (Node b) #

sequenceA :: Applicative f => Node (f a) -> f (Node a) #

mapM :: Monad m => (a -> m b) -> Node a -> m (Node b) #

sequence :: Monad m => Node (m a) -> m (Node a) #

TraversableWithLoc Node Source # 
Instance details

Defined in Zinza.Node

Methods

traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Node a -> f (Node b) Source #

Show a => Show (Node a) Source # 
Instance details

Defined in Zinza.Node

Methods

showsPrec :: Int -> Node a -> ShowS #

show :: Node a -> String #

showList :: [Node a] -> ShowS #

type Nodes a = [Node a] Source #

A list of Nodes.

data Expr a Source #

Expressions in templates.

Note: there are only eliminators; we cannot construct "bigger" expressions.

Constructors

EVar (Located a)

variable

EField (LExpr a) (Located Var)

field accessor

EApp (LExpr a) (LExpr a)

function application

Instances
Monad Expr Source #

Monad instance gives substitution.

Instance details

Defined in Zinza.Expr

Methods

(>>=) :: Expr a -> (a -> Expr b) -> Expr b #

(>>) :: Expr a -> Expr b -> Expr b #

return :: a -> Expr a #

fail :: String -> Expr a #

Functor Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

fmap :: (a -> b) -> Expr a -> Expr b #

(<$) :: a -> Expr b -> Expr a #

Applicative Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

pure :: a -> Expr a #

(<*>) :: Expr (a -> b) -> Expr a -> Expr b #

liftA2 :: (a -> b -> c) -> Expr a -> Expr b -> Expr c #

(*>) :: Expr a -> Expr b -> Expr b #

(<*) :: Expr a -> Expr b -> Expr a #

Foldable Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

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 #

toList :: Expr a -> [a] #

null :: Expr a -> Bool #

length :: Expr a -> Int #

elem :: Eq a => a -> Expr a -> Bool #

maximum :: Ord a => Expr a -> a #

minimum :: Ord a => Expr a -> a #

sum :: Num a => Expr a -> a #

product :: Num a => Expr a -> a #

Traversable Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

traverse :: Applicative f => (a -> f b) -> Expr a -> f (Expr b) #

sequenceA :: Applicative f => Expr (f a) -> f (Expr a) #

mapM :: Monad m => (a -> m b) -> Expr a -> m (Expr b) #

sequence :: Monad m => Expr (m a) -> m (Expr a) #

TraversableWithLoc Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Expr a -> f (Expr b) Source #

Show a => Show (Expr a) Source # 
Instance details

Defined in Zinza.Expr

Methods

showsPrec :: Int -> Expr a -> ShowS #

show :: Expr a -> String #

showList :: [Expr a] -> ShowS #

type LExpr a = Located (Expr a) Source #

Located expression.

Types

Zinza's type-system is delibarately extremely simple.

data Ty Source #

Zinza types.

The selectors tell how the Haskell value can be converted to primitive value. E.g.

>>> toType (Proxy :: Proxy Char)
TyString (Just "return")

Here, return converts Char to String.

Constructors

TyBool

boolean

TyString (Maybe Selector)

string

TyList (Maybe Selector) Ty

lists

TyRecord (Map Var (Selector, Ty))

records

TyFun Ty Ty

functions

Instances
Eq Ty Source # 
Instance details

Defined in Zinza.Type

Methods

(==) :: Ty -> Ty -> Bool #

(/=) :: Ty -> Ty -> Bool #

Ord Ty Source # 
Instance details

Defined in Zinza.Type

Methods

compare :: Ty -> Ty -> Ordering #

(<) :: Ty -> Ty -> Bool #

(<=) :: Ty -> Ty -> Bool #

(>) :: Ty -> Ty -> Bool #

(>=) :: Ty -> Ty -> Bool #

max :: Ty -> Ty -> Ty #

min :: Ty -> Ty -> Ty #

Show Ty Source # 
Instance details

Defined in Zinza.Type

Methods

showsPrec :: Int -> Ty -> ShowS #

show :: Ty -> String #

showList :: [Ty] -> ShowS #

displayTy :: Ty -> String Source #

Pretty print Ty.

Values

Values are passed at run-time, when the template is interpreted. When compiled to the Haskell module, Values aren't used.

data Value Source #

Template values.

Constructors

VBool Bool

booleans

VString String

strings

VList [Value]

lists

VRecord (Map Var Value)

records

VFun (Value -> Either RuntimeError Value)

function

Errors

class AsRuntimeError e where Source #

Class representing errors containing RuntimeErrors.

Without bugs, compiled template should not throw any RuntimeErrors, as they are prevented statically, i.e. reported already as CompileErrors.

class Monad m => ThrowRuntime m where Source #

Instances
ThrowRuntime IO Source # 
Instance details

Defined in Zinza.Errors

AsRuntimeError e => ThrowRuntime (Either e) Source # 
Instance details

Defined in Zinza.Errors

ThrowRuntime m => ThrowRuntime (StateT s m) Source # 
Instance details

Defined in Zinza.Errors

Location

data Loc Source #

Location, line and column.

Constructors

Loc !Int !Int 
Instances
Eq Loc Source # 
Instance details

Defined in Zinza.Pos

Methods

(==) :: Loc -> Loc -> Bool #

(/=) :: Loc -> Loc -> Bool #

Show Loc Source # 
Instance details

Defined in Zinza.Pos

Methods

showsPrec :: Int -> Loc -> ShowS #

show :: Loc -> String #

showList :: [Loc] -> ShowS #

data Located a Source #

Located element.

Constructors

L !Loc a 
Instances
Functor Located Source # 
Instance details

Defined in Zinza.Pos

Methods

fmap :: (a -> b) -> Located a -> Located b #

(<$) :: a -> Located b -> Located a #

Foldable Located Source # 
Instance details

Defined in Zinza.Pos

Methods

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 #

toList :: Located a -> [a] #

null :: Located a -> Bool #

length :: Located a -> Int #

elem :: Eq a => a -> Located a -> Bool #

maximum :: Ord a => Located a -> a #

minimum :: Ord a => Located a -> a #

sum :: Num a => Located a -> a #

product :: Num a => Located a -> a #

Traversable Located Source # 
Instance details

Defined in Zinza.Pos

Methods

traverse :: Applicative f => (a -> f b) -> Located a -> f (Located b) #

sequenceA :: Applicative f => Located (f a) -> f (Located a) #

mapM :: Monad m => (a -> m b) -> Located a -> m (Located b) #

sequence :: Monad m => Located (m a) -> m (Located a) #

Eq a => Eq (Located a) Source # 
Instance details

Defined in Zinza.Pos

Methods

(==) :: Located a -> Located a -> Bool #

(/=) :: Located a -> Located a -> Bool #

Show a => Show (Located a) Source # 
Instance details

Defined in Zinza.Pos

Methods

showsPrec :: Int -> Located a -> ShowS #

show :: Located a -> String #

showList :: [Located a] -> ShowS #

zeroLoc :: Loc Source #

Unknown location.

displayLoc :: Loc -> String Source #

Pretty-print location.

class Traversable t => TraversableWithLoc t where Source #

Some containers have location for each element.

Methods

traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> t a -> f (t b) Source #

Instances
TraversableWithLoc Expr Source # 
Instance details

Defined in Zinza.Expr

Methods

traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Expr a -> f (Expr b) Source #

TraversableWithLoc Node Source # 
Instance details

Defined in Zinza.Node

Methods

traverseWithLoc :: Applicative f => (Loc -> a -> f b) -> Node a -> f (Node b) Source #

Variables

type Var = String Source #

Variable name, possibly a fieldname in the record.

type Selector = String Source #

Haskell selector.