packages feed

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 []