{-# LANGUAGE |
| ViewPatterns, |
| KindSignatures, |
| TypeOperators, |
| DataKinds, |
| FlexibleInstances, |
| FlexibleContexts, |
| PatternSynonyms, |
| ConstraintKinds, |
| ScopedTypeVariables, |
|
|
| BangPatterns |
| #-} |
|
|
| module Main where |
|
|
| import Prelude |
|
|
| import GHC.TypeLits ( Symbol |
| , KnownSymbol |
| , symbolVal |
| ) |
| import Data.Kind ( Type ) |
| import Data.Maybe |
| import Data.Proxy |
| import Data.Dynamic |
|
|
|
|
| -- artifacts for named arguments |
|
|
| newtype NamedArg (t :: Type) (name :: Symbol) = NamedArg t |
| type name !: t = NamedArg t name |
| type name ?: t = NamedArg (Maybe t) name |
|
|
| pattern Arg :: t -> name !: t |
| pattern Arg t = NamedArg t |
| {-# COMPLETE Arg #-} |
|
|
| arg :: name !: t -> t |
| arg (NamedArg a) = a |
|
|
| optionalArg :: name ?: t -> Maybe t |
| optionalArg (NamedArg !ma) = ma |
|
|
| defaultArg :: t -> name ?: t -> t |
| defaultArg !a (NamedArg !ma) = fromMaybe a ma |
|
|
|
|
| -- * minimum data structures as interface with scripting code |
|
|
| type AttrKey = String |
| data AttrVal = NilValue |
| | IntValue !Integer |
| | StrValue !String |
| deriving (Eq, Ord, Typeable) |
| instance Show AttrVal where |
| show NilValue = "nil" |
| show (IntValue !x) = show x |
| show (StrValue !x) = show x |
|
|
|
|
| data ArgsPack = ArgsPack { |
| positional'args :: [AttrVal] |
| , keyword'args :: [(AttrKey, AttrVal)] |
| } |
| instance Semigroup ArgsPack where |
| (ArgsPack p1 kw1) <> (ArgsPack p2 kw2) = ArgsPack (p1 ++ p2) (kw1 ++ kw2) |
| instance Monoid ArgsPack where |
| mempty = ArgsPack [] [] |
|
|
| takeKwArg |
| :: AttrKey -> [(AttrKey, AttrVal)] -> (Maybe AttrVal, [(AttrKey, AttrVal)]) |
| takeKwArg !k !kwargs = go [] kwargs |
| where |
| go |
| :: [(AttrKey, AttrVal)] |
| -> [(AttrKey, AttrVal)] |
| -> (Maybe AttrVal, [(AttrKey, AttrVal)]) |
| go _ [] = (Nothing, kwargs) |
| go others (p@(!key, !val) : kwargs') = if key == k |
| then (Just val, reverse others ++ kwargs') |
| else go (p : others) kwargs' |
|
|
|
|
| type ContProc = (AttrVal -> IO ()) -> IO () |
|
|
| -- | Haskell functions callable with an apk |
| class Callable fn where |
| call :: fn -> ArgsPack -> ContProc |
|
|
| -- instance for nullary functions, which is the base case |
| instance Callable ContProc where |
| call !fn (ArgsPack !args !kwargs) exit = |
| if null args && null kwargs then fn exit else error "extraneous args" |
|
|
| -- instance for repacking arg receiver |
| instance Callable fn' => Callable (ArgsPack -> fn') where |
| call !fn !apk !exit = call (fn apk) (ArgsPack [] []) exit |
|
|
| -- instances for positional arg receivers |
|
|
| instance Callable fn' => Callable (AttrVal -> fn') where |
| call !fn (ArgsPack (val : args) !kwargs) !exit = |
| call (fn val) (ArgsPack args kwargs) exit |
| call _ _ _ = error "missing anonymous arg" |
|
|
| instance Callable fn' => Callable (Maybe AttrVal -> fn') where |
| call !fn (ArgsPack [] !kwargs) !exit = |
| call (fn Nothing) (ArgsPack [] kwargs) exit |
| call !fn (ArgsPack (val : args) !kwargs) !exit = |
| call (fn (Just val)) (ArgsPack args kwargs) exit |
|
|
| instance Callable fn' => Callable (String -> fn') where |
| call !fn (ArgsPack (val : args) !kwargs) !exit = case val of |
| StrValue !val' -> call (fn val') (ArgsPack args kwargs) exit |
| _ -> error "arg type mismatch" |
| call _ _ _ = error "missing anonymous arg" |
|
|
| instance Callable fn' => Callable (Maybe String -> fn') where |
| call !fn (ArgsPack [] !kwargs) !exit = |
| call (fn Nothing) (ArgsPack [] kwargs) exit |
| call !fn (ArgsPack (val : args) !kwargs) !exit = case val of |
| StrValue !val' -> call (fn (Just val')) (ArgsPack args kwargs) exit |
| _ -> error "arg type mismatch" |
|
|
| -- todo instances for receivers of positional arg of (Maybe) Integer |
| -- type, and other types covered by AttrVal |
|
|
| -- instances for keyword arg receivers |
|
|
| instance (KnownSymbol name, Callable fn') => Callable (NamedArg AttrVal name -> fn') where |
| call !fn (ArgsPack !args !kwargs) !exit = case takeKwArg argName kwargs of |
| (Just !val, kwargs') -> |
| call (fn (NamedArg val)) (ArgsPack args kwargs') exit |
| (Nothing, kwargs') -> case args of |
| [] -> error $ "missing named arg: " <> argName |
| (val : args') -> call (fn (NamedArg val)) (ArgsPack args' kwargs') exit |
| where !argName = symbolVal (Proxy :: Proxy name) |
|
|
| instance (KnownSymbol name, Callable fn') => Callable (NamedArg (Maybe AttrVal) name -> fn') where |
| call !fn (ArgsPack !args !kwargs) !exit = case takeKwArg argName kwargs of |
| (Nothing, !kwargs') -> case args of |
| [] -> call (fn (NamedArg Nothing)) (ArgsPack [] kwargs') exit |
| val : args' -> |
| call (fn (NamedArg (Just val))) (ArgsPack args' kwargs') exit |
| (!maybeVal, !kwargs') -> |
| call (fn (NamedArg maybeVal)) (ArgsPack args kwargs') exit |
| where !argName = symbolVal (Proxy :: Proxy name) |
|
|
| instance (KnownSymbol name, Callable fn') => Callable (NamedArg String name -> fn') where |
| call !fn (ArgsPack !args !kwargs) !exit = case takeKwArg argName kwargs of |
| (Just !val, !kwargs') -> case val of |
| StrValue !val' -> call (fn (NamedArg val')) (ArgsPack args kwargs') exit |
| _ -> error "arg type mismatch" |
| (Nothing, !kwargs') -> case args of |
| [] -> error $ "missing named arg: " <> argName |
| val : args' -> case val of |
| StrValue !val' -> |
| call (fn (NamedArg val')) (ArgsPack args' kwargs') exit |
| _ -> error "arg type mismatch" |
| where !argName = symbolVal (Proxy :: Proxy name) |
|
|
| instance (KnownSymbol name, Callable fn') => Callable (NamedArg (Maybe String) name -> fn') where |
| call !fn (ArgsPack !args !kwargs) !exit = case takeKwArg argName kwargs of |
| (Just !val, !kwargs') -> case val of |
| StrValue !val' -> |
| call (fn (NamedArg (Just val'))) (ArgsPack args kwargs') exit |
| _ -> error "arg type mismatch" |
| (Nothing, !kwargs') -> case args of |
| [] -> call (fn (NamedArg Nothing)) (ArgsPack [] kwargs') exit |
| val : args' -> case val of |
| StrValue !val' -> |
| call (fn (NamedArg (Just val'))) (ArgsPack args' kwargs') exit |
| _ -> error "arg type mismatch" |
| where !argName = symbolVal (Proxy :: Proxy name) |
|
|
| -- todo instances for receivers of keyword arg of (Maybe) Integer |
| -- type, and other types covered by AttrVal |
|
|
|
|
| -- * functions to be callable from scripting code |
|
|
| -- | interfacing Haskell function meant to be easily called by scripting code |
| assert |
| :: "expect" !: AttrVal |
| -> "target" ?: AttrVal |
| -> "message" ?: String |
| -> (AttrVal -> IO ()) |
| -> IO () |
| assert (Arg !expect) (optionalArg -> !maybeTarget) (defaultArg "sth ought to be" -> !message) !exit |
| = case maybeTarget of |
| Nothing -> case expect of |
| NilValue -> error $ "* assertion failed: " <> message |
| IntValue 0 -> error $ "* assertion failed: " <> message |
| StrValue "" -> error $ "* assertion failed: " <> message |
| _ -> exit $ StrValue $ "* assertion passed: " <> message |
| Just target -> if expect == target |
| then exit $ StrValue $ "* assertion passed: " <> message |
| else error $ "* assertion failed: " <> message |
|
|
|
|
| -- mockup & test out |
| main :: IO () |
| main = do |
| call assert apk1 $ \ !result -> putStrLn $ "Got result1: " <> show result |
| call assert apk2 $ \ !result -> putStrLn $ "Got result2: " <> show result |
| call assert apk3 $ \ !result -> putStrLn $ "Got result3: " <> show result |
| call assert apk4 $ \ !result -> putStrLn $ "Got result4: " <> show result |
|
|
| where |
|
|
| !apk1 = ArgsPack |
| [] |
| [ ("message", StrValue "as good will") |
| , ("target" , IntValue 333) |
| , ("expect" , IntValue 333) |
| ] |
| !apk2 = ArgsPack [IntValue 333, IntValue 333, StrValue "as good will"] [] |
| !apk3 = ArgsPack [IntValue 333] [("target", IntValue 333)] |
| !apk4 = ArgsPack [] [("target", IntValue 333), ("expect", IntValue 555)] |