implicit-0.0.2: Graphics/Implicit/ExtOpenScad/Util/ArgParser.hs
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances, ScopedTypeVariables #-}
-- | We're going to give ourselves all sorts of goodies to parse OpenSCAD style module arguments here!!!
-- To see the awesomeness of this applied, look at Primitives
--
-- Our tool of choice is ArgParser.
-- It handles argument input, but also examples and unit tests for modules
module Graphics.Implicit.ExtOpenScad.Util.ArgParser (
-- $ Note: The actual definition of ArgParser is in Defintions,
-- to avoid the pain of circular dependencies.
-- * ArgParser building functions
-- ** argument & combinators
argument,
doc,
defaultTo,
-- ** example
example,
-- ** test & combinators
test,
eulerCharacteristic,
-- * Tools for handeling ArgParsers
argMap,
getArgParserDocs,
Doc (..), DocPart (..)
)where
import Graphics.Implicit.ExtOpenScad.Definitions
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Control.Exception as Ex
-- * Instance Declarations
-- | ArgParser is a monad.
-- In some ways, an applicative functor would be nicer -- extracting docs
-- would be less crazy --, but we want do notation for prettiness.
instance Monad ArgParser where
-- return is easy: if we want an ArgParser that just gives us a, that is
-- ArgParserTerminator a
return a = ArgParserTerminator a
-- Now things get more interesting. We need to describe how (>>=) works.
-- Let's get the hard ones out of the way first.
-- ArgParser actually
(ArgParser str fallback doc f) >>= g = ArgParser str fallback doc (\a -> (f a) >>= g)
(ArgParserFailIf b errmsg child) >>= g = ArgParserFailIf b errmsg (child >>= g)
-- These next to is easy, they just pass the work along to their child
(ArgParserExample str child) >>= g = ArgParserExample str (child >>= g)
(ArgParserTest str tests child) >>= g = ArgParserTest str tests (child >>= g)
-- And an ArgParserTerminator happily gives away the value it contains
(ArgParserTerminator a) >>= g = g a
-- * ArgParser building functions
-- ** argument and combinators
argument :: forall desiredType. (OTypeMirror desiredType) => String -> ArgParser desiredType
argument name =
ArgParser name Nothing "" $ \oObjVal -> do
let
val = fromOObj oObjVal :: Maybe desiredType
errmsg = case oObjVal of
OError errs -> "error in computing value for arugment " ++ name
++ ": " ++ concat errs
_ -> "arg " ++ show oObjVal ++ " not compatible with " ++ name
-- Using /= Nothing would require Eq desiredType
ArgParserFailIf (Maybe.isNothing val) errmsg $ ArgParserTerminator $ (\(Just a) -> a) val
doc (ArgParser name defMaybeVal oldDoc next) doc =
ArgParser name defMaybeVal doc next
defaultTo :: forall a. (OTypeMirror a) => ArgParser a -> a -> ArgParser a
defaultTo (ArgParser name oldDefMaybeVal doc next) newDefVal =
ArgParser name (Just $ toOObj newDefVal) doc next
-- ** example
example :: String -> ArgParser ()
example str = ArgParserExample str (return ())
-- * test and combinators
test :: String -> ArgParser ()
test str = ArgParserTest str [] (return ())
eulerCharacteristic :: ArgParser a -> Int -> ArgParser a
eulerCharacteristic (ArgParserTest str tests child) χ =
ArgParserTest str ((EulerCharacteristic χ) : tests) child
-- * Tools for handeling ArgParsers
-- | Apply arguments to an ArgParser
argMap ::
[OpenscadObj] -- ^ Unnamed Arguments
-> [(String, OpenscadObj)] -- ^ Named Arguments
-> ArgParser a -- ^ ArgParser to apply them to
-> (Maybe a, [String]) -- ^ (result, error messages)
argMap a b = argMap2 a (Map.fromList b)
argMap2 :: [OpenscadObj] -> Map.Map String OpenscadObj -> ArgParser a -> (Maybe a, [String])
argMap2 unnamedArgs namedArgs (ArgParser name fallback _ f) =
case Map.lookup name namedArgs of
Just a -> argMap2
unnamedArgs
(Map.delete name namedArgs)
(f a)
Nothing -> case unnamedArgs of
x:xs -> argMap2 xs namedArgs (f x)
[] -> case fallback of
Just b -> argMap2 [] namedArgs (f b)
Nothing -> (Nothing, ["No value and no default for argument " ++ name])
argMap2 a b (ArgParserTerminator val) =
(Just val,
if length a + Map.size b > 0
then ["unused arguments"]
else []
)
argMap2 a b (ArgParserFailIf test err child) =
if test
then (Nothing, [err])
else argMap2 a b child
argMap2 a b (ArgParserExample str child) = argMap2 a b child
argMap2 a b (ArgParserTest str tests child) = argMap2 a b child
-- | We need a format to extract documentation into
data Doc = Doc String [DocPart]
deriving (Show)
data DocPart = ExampleDoc String
| ArgumentDoc String (Maybe String) String
deriving (Show)
-- Here there be dragons!
-- Because we made this a Monad instead of applicative functor, there's now sane way to do this.
-- We give undefined (= an error) and let laziness prevent if from ever being touched.
-- We're using IO so that we can catch an error if this backfires.
-- If so, we *back off*.
-- | Extract Documentation from an ArgParser
getArgParserDocs ::
(ArgParser a) -- ^ ArgParser
-> IO [DocPart] -- ^ Docs (sadly IO wrapped)
getArgParserDocs (ArgParser name fallback doc fnext) =
do
otherDocs <- Ex.catch (getArgParserDocs $ fnext undefined) (\(e :: Ex.SomeException) -> return [])
return $ (ArgumentDoc name (fmap show fallback) doc):otherDocs
getArgParserDocs (ArgParserExample str child) =
do
childResults <- getArgParserDocs child
return $ (ExampleDoc str) : childResults
-- We try to look at as little as possible, to avoid the risk of triggering an error.
-- Yay laziness!
getArgParserDocs (ArgParserTest _ _ child ) = getArgParserDocs child
getArgParserDocs (ArgParserFailIf _ _ child ) = getArgParserDocs child
-- To look at this one would almost certainly be death (exception)
getArgParserDocs (ArgParserTerminator _ ) = return []