implicit-0.4.1.0: Graphics/Implicit/ExtOpenScad/Eval/Expr.hs
{- ORMOLU_DISABLE -}
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Allow us to use string literals for Text
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Implicit.ExtOpenScad.Eval.Expr (evalExpr, rawRunExpr, matchPat, StateE, ExprState(ExprState), addMessage) where
import Prelude (String, Maybe(Just, Nothing), Bool (True), ($), elem, pure, zip, (&&), const, (<>), foldr, foldMap, (.), (<$>), traverse)
import Graphics.Implicit.ExtOpenScad.Definitions (
Pattern(Name, ListP, Wild),
OVal(OList, OError, OFunc, OUndefined),
Expr(LitE, ListE, LamE, Var, (:$)),
Symbol(Symbol),
VarLookup(VarLookup),
SourcePosition,
Message(Message),
MessageType(Error),
StateC, ImplicitCadM, runImplicitCadM
)
import Graphics.Implicit.ExtOpenScad.Util.OVal (oTypeStr, getErrors)
import Graphics.Implicit.ExtOpenScad.Util.StateC (getVarLookup)
import qualified Graphics.Implicit.ExtOpenScad.Util.StateC as GIEUS (addMessage)
import Data.Maybe (fromMaybe, isNothing)
import Data.Map (fromList, lookup)
import Data.Foldable (fold, traverse_)
import Data.Traversable (for)
import Control.Monad (zipWithM)
import Data.Text.Lazy (Text, unpack)
import Data.Eq (Eq, (==))
import Text.Show (Show)
import Control.Monad.Writer.Class (tell)
import Control.Monad.State.Lazy (get)
import Control.Monad.State.Class (modify)
import Control.Monad.Identity (Identity (runIdentity))
import Control.Monad.Reader (ask)
-- Patterns is the only thing being modified, so
-- it is the only on in the state structure.
newtype ExprState = ExprState
{ patterns :: [String]
} deriving (Eq, Show)
-- varLookup and sourcePos are only ever read from
-- so we can put them into a reader, so they can never
-- accidentally be written to.
data Input = Input
{ varLookup :: VarLookup
, sourcePos :: SourcePosition
} deriving (Eq, Show)
-- Check Graphics.Implicit.ExtOpenScad.Definitions for an explanation
-- of why we are using a transformer stack.
type StateE a = ImplicitCadM Input [Message] ExprState Identity a
runStateE :: Input -> ExprState -> StateE a -> (a, [Message], ExprState)
runStateE r s m = runIdentity $ runImplicitCadM r s m
-- Add a message to our list of messages contained in the StatE monad.
addMessage :: MessageType -> SourcePosition -> Text -> StateE ()
addMessage mtype pos text = addMesg $ Message mtype pos text
where
addMesg :: Message -> StateE ()
addMesg = tell . pure
-- Log an error condition.
errorE :: SourcePosition -> Text -> StateE ()
errorE = addMessage Error
-- | The names of all of the patterns in the given pattern.
patVars :: Pattern -> [Text]
patVars (Name (Symbol name)) = [name]
patVars (ListP pats) = foldMap patVars pats
patVars Wild = []
-- | Match patterns and ovals, returning a list of all of the OVals matched.
patMatch :: Pattern -> OVal -> Maybe [OVal]
patMatch (Name _) val = Just [val]
patMatch (ListP pats) (OList vals) = fold <$> zipWithM patMatch pats vals
patMatch Wild _ = Just []
patMatch _ _ = Nothing
-- | Construct a VarLookup from the given Pattern and OVal, if possible.
matchPat :: Pattern -> OVal -> Maybe VarLookup
matchPat pat val = VarLookup . fromList . zip (Symbol <$> patVars pat) <$> patMatch pat val
-- | The entry point from StateC. evaluates an expression, pureing the result, and moving any error messages generated into the calling StateC.
evalExpr :: SourcePosition -> Expr -> StateC OVal
evalExpr pos expr = do
vars <- getVarLookup
let
input = Input vars pos
initState = ExprState []
(valf, messages, _) = runStateE input initState (evalExpr' expr)
moveMessage (Message mtype mpos text) = GIEUS.addMessage mtype mpos text
traverse_ moveMessage messages
pure $ valf []
-- A more raw entry point, that does not depend on IO.
rawRunExpr :: SourcePosition -> VarLookup -> Expr -> (OVal, [Message])
rawRunExpr pos vars expr = do
let
input = Input vars pos
initState = ExprState []
(valf, messages, _) = runStateE input initState (evalExpr' expr)
(valf [], messages)
-- The expression evaluators.
evalExpr' :: Expr -> StateE ([OVal] -> OVal)
-- Evaluate a variable lookup.
evalExpr' (Var (Symbol name)) = do
Input (VarLookup varlookup) spos <- ask
(ExprState namestack) <- get
let v = lookup (Symbol name) varlookup
n = unpack name `elem` namestack
case (v, n) of
(_, True) -> pure $ \l ->
let m = foldr
-- Scan for variable names from the end of the list (newest), and also
-- ensure that we aren't overriding values if we have already found one.
-- All in all, this should ensure that we aren't seeing the off by 1 error
-- when looking up the values for function parameters as raised in this issue.
-- https://github.com/Haskell-Things/ImplicitCAD/issues/431
(\(n', v') z -> if isNothing z && unpack name == n' then pure v' else z)
Nothing $
-- Zip the names and incoming values so that when looking up values
-- we are ensuring that names are paired with values. When a LamE is evaled
-- it is possible that a name is pushed and then used before a value is pushed
-- and this zip neatly handles that situation.
zip namestack l
in fromMaybe OUndefined m
(Just o, _) -> pure $ const o
_ -> do
errorE spos ("Variable " <> name <> "not in scope")
pure $ const OUndefined
-- Evaluate a literal value.
evalExpr' (LitE val) = pure $ const val
-- Evaluate a list of expressions.
evalExpr' (ListE exprs) = do
valFuncs <- traverse evalExpr' exprs
pure $ \s -> OList $ ($ s) <$> valFuncs
-- Evaluate application of a function.
evalExpr' (fexpr :$ argExprs) = do
fValFunc <- evalExpr' fexpr
argValFuncs <- traverse evalExpr' argExprs
pure $ \s -> app (fValFunc s) (($ s) <$> argValFuncs)
where
app f l = case (getErrors f, getErrors $ OList l) of
(Nothing, Nothing) -> app' f l
where
-- apply function to the list of its arguments until we run out
-- of them
app' (OFunc f') (x:xs) = app (f' x) xs
app' a [] = a
app' x _ = OError $ "Can't apply arguments to " <> oTypeStr x
(Just err, _ ) -> OError err
(_, Just err) -> OError err
-- Evaluate a lambda function.
evalExpr' (LamE pats fexpr) = do
fparts <- for pats $ \pat -> do
-- Add new names to the end of the list so that names and values aren't
-- effectively shifted by 1 when a name is defined but the value hasn't been
-- calculated yet. This also allows us to neatly zip names and values ensuring
-- we are only looking at names with defined values.
modify $ \s -> s { patterns = patterns s <> (unpack <$> patVars pat)}
pure $ \f xss -> OFunc $ \val -> case patMatch pat val of
-- Push values to the end once they are calculated.
Just xs -> f (xss <> xs)
Nothing -> OError "Pattern match failed"
fval <- evalExpr' fexpr
pure $ foldr ($) fval fparts