-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
-- We'd like to parse openscad code, with some improvements, for backwards compatability.
-- Implement statements for things other than primitive objects!
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances, ScopedTypeVariables, NoMonomorphismRestriction #-}
module Graphics.Implicit.ExtOpenScad.Statements where
import Prelude hiding (lookup)
import Graphics.Implicit.Definitions
import Graphics.Implicit.ObjectUtil (getBox2, getBox3)
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Expressions
import Graphics.Implicit.ExtOpenScad.Util
import Graphics.Implicit.ExtOpenScad.Primitives
import qualified Graphics.Implicit.Primitives as Prim
import Data.Map (Map, lookup, insert, union)
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Control.Monad (liftM)
import System.Plugins.Load (load_, LoadStatus(..))
import Control.Monad (forM_)
import Graphics.Implicit.ExtOpenScad.Util.ArgParser
import Graphics.Implicit.ExtOpenScad.Util.Computation
tryMany = (foldl1 (<|>)) . (map try)
-- | A statement in our programming openscad-like programming language.
computationStatement :: GenParser Char st ComputationStateModifier
computationStatement =
(try $ do -- suite statemetns: no semicolon...
many space
s <- tryMany [
ifStatement,
forStatement,
throwAway,
userModuleDeclaration,
unimplemented "mirror",
unimplemented "multmatrix",
unimplemented "color",
unimplemented "render",
unimplemented "surface",
unimplemented "projection",
unimplemented "rotate_extrude",
unimplemented "import_stl"
-- rotateExtrudeStatement
]
many space
return s
) <|> (try $ do -- Non suite statements. Semicolon needed...
many space
s <- tryMany [
echoStatement,
assigmentStatement,
includeStatement,
useStatement
]
many space
char ';'
many space
return s
)<|> (try $ many space >> comment)
<|> (try $ do
many space
s <- userModule
many space
return s
)
-- | A suite of statements!
-- What's a suite? Consider:
--
-- union() {
-- sphere(3);
-- }
--
-- The suite was in the braces ({}). Similarily, the
-- following has the same suite:
--
-- union() sphere(3);
--
-- We consider it to be a list of statements which
-- are in tern ComputationStateModifier s.
-- So this parses them.
suite :: GenParser Char st [ComputationStateModifier]
suite = (liftM return computationStatement <|> do
char '{'
many space
stmts <- many (try computationStatement)
many space
char '}'
return stmts
) > "statement suite"
-- | We think of comments as statements that do nothing. It's just convenient.
comment =
(((try $ do
string "//"
many ( noneOf "\n")
string "\n"
) <|> (do
string "/*"
manyTill anyChar (try $ string "*/")
)) >> return id) > "comment"
throwAway :: GenParser Char st ComputationStateModifier
throwAway = do
many space
oneOf "%*"
many space
computationStatement
return id
-- An included statement! Basically, inject another openscad file here...
includeStatement :: GenParser Char st ComputationStateModifier
includeStatement = (do
line <- fmap sourceLine getPosition
string "include"
many space
string "<"
filename <- many (noneOf "<>")
string ">"
return $ \ ioWrappedState -> do
state@(varlookup,obj2s,obj3s) <- ioWrappedState;
case reverse filename of
'o':'.':_ -> do
loaded :: LoadStatus VariableLookup
<- load_ filename ["."] "openscadAPI"
case loaded of
LoadFailure errs -> do
putStrLn $ show errs
return state
LoadSuccess _ newapi -> do
putStrLn "Loaded Haskell Module..."
return (union varlookup newapi, obj2s, obj3s)
_ -> do
content <- readFile filename
case parse (many1 computationStatement) "" content of
Left err -> do
errorMessage line $
"Error parsing included file " ++ filename ++ "\n"
++ show err
++ "Ignoring included file " ++ filename ++ "..."
return state
Right result -> runComputations (return state) result
) > "include statement"
-- In a use statement, variables are imported but we drop any existing 2D/3D objects.
useStatement :: GenParser Char st ComputationStateModifier
useStatement = (do
line <- fmap sourceLine getPosition
string "use"
many space
string "<"
filename <- many (noneOf "<>")
string ">"
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState;
content <- readFile filename
case parse (many1 computationStatement) "" content of
Left err -> do
errorMessage line $
"Error parsing included file " ++ filename ++ "\n"
++ show err
++ "Ignoring included file " ++ filename ++ "..."
return state
Right result -> runComputations (return (varlookup,[],[])) result
) > "use statement"
-- | An assignment statement (parser)
assigmentStatement :: GenParser Char st ComputationStateModifier
assigmentStatement =
(try $ do
line <- fmap sourceLine getPosition
pattern <- patternMatcher
many space
char '='
many space
valExpr <- expression 0
return $ \ ioWrappedState -> do
state@(varlookup, obj2s, obj3s) <- ioWrappedState
let
val = valExpr varlookup
match = pattern val
case match of
Just dictWithNew -> case val of
OError e -> do
errorMessage line $
"error in evaluating assignment statement assigned value:"
++ concat (map ("\n "++) e)
return (union dictWithNew varlookup, obj2s, obj3s)
_ -> return (union dictWithNew varlookup, obj2s, obj3s)
Nothing -> do
errorMessage line $ "pattern match fail in assignment statement"
return state
) <|> (try $ do
line <- fmap sourceLine getPosition
varSymb <- (try $ string "function" >> many1 space >> variableSymb)
<|> variableSymb
many space
char '('
many space
argVars <- sepBy variableSymb (many space >> char ',' >> many space)
many space
char ')'
many space
char '='
many space
valExpr <- expression 0
return $ \ ioWrappedState -> do
(varlookup, obj2s, obj3s) <- ioWrappedState
let
makeFunc baseExpr (argVar:xs) varlookup' = OFunc $
\argObj -> makeFunc baseExpr xs (insert argVar argObj varlookup')
makeFunc baseExpr [] varlookup' = baseExpr varlookup'
val = makeFunc valExpr argVars varlookup
case val of
OError e -> do
errorMessage line $ "error in evaluating assignment statement assigned value:"
++ concat (map ("\n "++) e)
return (insert varSymb val varlookup, obj2s, obj3s)
_ -> return (insert varSymb val varlookup, obj2s, obj3s)
)> "assignment statement"
-- | An echo statement (parser)
echoStatement :: GenParser Char st ComputationStateModifier
echoStatement = do
line <- fmap sourceLine getPosition
string "echo"
many space
char '('
many space
exprs <- expression 0 `sepBy` (many space >> char ',' >> many space)
many space
char ')'
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState
let
vals = map ($varlookup) exprs
isError (OError _) = True
isError _ = False
show2 (OString str) = str
show2 a = show a
errorMessage line $
if any isError vals
then
"in module echo:"
++ ( concat $ concat $
map (map ("\n "++)) $
map (\(OError errs) -> errs) $ filter isError vals
)
else
unwords $ map show2 vals
return state
ifStatement :: GenParser Char st ComputationStateModifier
ifStatement = (do
line <- fmap sourceLine getPosition
string "if"
many space
char '('
bexpr <- expression 0
char ')'
many space
statementsTrueCase <- suite
many space
statementsFalseCase <- try (string "else" >> many space >> suite ) <|> (return [])
return $ \ ioWrappedState -> do
state@(varlookup, _, _) <- ioWrappedState
case bexpr varlookup of
OBool bval ->
if bval
then runComputations (return state) statementsTrueCase
else runComputations (return state) statementsFalseCase
OError errs -> do
errorMessage line $ " error while evaluating if statement conditional:"
++ concat (map ("\n " ++) errs)
return state
obj -> do
errorMessage line $ "inappropriate type for if statement conditional:\n"
++ " value " ++ show obj ++ " is not a boolean."
return state
) > "if statement"
forStatement :: GenParser Char st ComputationStateModifier
forStatement = (do
line <- fmap sourceLine getPosition
-- a for loop is of the form:
-- for ( vsymb = vexpr ) loopStatements
-- eg. for ( a = [1,2,3] ) {echo(a); echo "lol";}
-- eg. for ( [a,b] = [[1,2]] ) {echo(a+b); echo "lol";}
string "for"
many space
char '('
many space
pattern <- patternMatcher
many space
char '='
vexpr <- expression 0
char ')'
many space
loopStatements <- suite
return $ \ ioWrappedState -> do
-- a for loop unpackages the state from an io monad
state@(varlookup,_,_) <- ioWrappedState;
let
-- each iteration of the loop consists of unpacking the state
loopOnce ::
ComputationState -- ^ The state at this point in the loop
-> OpenscadObj -- ^ The value of vsymb for this iteration
-> ComputationState -- ^ The resulting state
loopOnce ioWrappedState val = do
state@(varlookup, a, b) <- ioWrappedState;
let
match = pattern val
vsymbSetState = case match of
Just dictWithNew -> return (union dictWithNew varlookup, a, b)
Nothing -> do
errorMessage line $ "Pattern match fail in for loop step"
return state
runComputations vsymbSetState loopStatements
-- Then loops once for every entry in vexpr
case vexpr varlookup of
OList l -> foldl (loopOnce) (return state) l
OError errs -> do
errorMessage line $ "Error while evaluating for loop array:"
++ concat (map ("\n " ++) errs)
return state
obj -> do
errorMessage line $ "Error in for loop iteration array:\n"
++ " Inappropriate type for loop iterated array:\n"
++ " value " ++ show obj ++ " is not a list."
return state
) > "for statement"
moduleWithSuite ::
String -> ([ComputationStateModifier] -> ArgParser ComputationStateModifier)
-> GenParser Char st ComputationStateModifier
moduleWithSuite name argHandeler = (do
line <- fmap sourceLine getPosition
string name;
many space;
(unnamed, named) <- moduleArgsUnit
many space;
statements <- suite
return $ \ ioWrappedState -> do
state@(varlookup, obj2s, obj3s) <- ioWrappedState
case argMap
(map ($varlookup) unnamed)
(map (\(a,b) -> (a, b varlookup)) named) (argHandeler statements)
of
(Just computationModifier, []) -> computationModifier (return state)
(Nothing, []) -> do
errorMessage line $ "Module " ++ name
++ " failed without a message"
return state
(Nothing, errs) -> do
errorMessage line $ "Module " ++ name
++ " failed with the following messages:"
++ concat (map (" "++) errs)
return state
(Just computationModifier, errs) -> do
errorMessage line $ "Module " ++ name
++ " gave the following warnings:"
++ concat (map (" "++) errs)
computationModifier (return state)
) > (name ++ " statement")
unimplemented :: String -> GenParser Char st ComputationStateModifier
unimplemented name = do
line <- fmap sourceLine getPosition
string name
many space;
moduleArgsUnit
many space;
(try suite <|> (many space >> char ';' >> return []))
return $ \ ioWrappedState -> do
state <- ioWrappedState
errorMessage line $ "OpenSCAD command " ++ name ++ " not yet implemented"
return state
userModule :: GenParser Char st ComputationStateModifier
userModule = do
line <- fmap sourceLine getPosition
name <- variableSymb;
many space;
(unnamed, named) <- moduleArgsUnit
many space;
statements <- ( try suite <|> (many space >> char ';' >> return []))
return $ \ ioWrappedState -> do
state@(varlookup, obj2s, obj3s) <- ioWrappedState
case lookup name varlookup of
Just (OModule m) ->
case argMap
(map ($varlookup) unnamed)
(map (\(a,b) -> (a, b varlookup)) named)
(m statements)
of
(Just computationModifier, []) ->
computationModifier (return state)
(Nothing, []) -> do
errorMessage line $ "Module " ++ name
++ " failed without a message"
return state
(Nothing, errs) -> do
errorMessage line $ "Module " ++ name
++ " failed with the following messages:"
++ concat (map (" "++) errs)
return state
(Just computationModifier, errs) -> do
errorMessage line $ "Module " ++ name
++ " gave the following warnings:"
++ concat (map (" "++) errs)
computationModifier (return state)
_ -> do
errorMessage line $ "module " ++ name ++ " is not in scope"
return state
userModuleDeclaration :: GenParser Char st ComputationStateModifier
userModuleDeclaration = do
string "module"
many space;
newModuleName <- variableSymb;
many space;
args <- moduleArgsUnitDecl
many space;
codeStatements <- suite
return $ \ envIOWrappedState -> do
(envVarlookup, envObj2s, envObj3s) <- envIOWrappedState
let
newModule = OModule $ \childrenStatements -> do
argVarlookupModifier <- args envVarlookup
return $ \contextIOWrappedState -> do
contextState@(contextVarLookup, contextObj2s, contextObj3s)
<- contextIOWrappedState
(_, childObj2s, childObj3s) <- runComputations
(return contextState)
childrenStatements;
let
children = ONum $ fromIntegral
(length childObj2s + length childObj3s)
child = OModule $ \suite -> do
n :: ℕ <- argument "n";
if n <= length childObj3s
then addObj3 (childObj3s !! n)
else addObj2 (childObj2s !! (n+1-length childObj3s))
childBox = OFunc $ \n -> case fromOObj n :: Maybe ℕ of
Just n | n < length childObj3s + length childObj2s ->
if n <= length childObj3s
then toOObj $ getBox3 (childObj3s !! n)
else toOObj $ getBox2 (childObj2s !! (n+1-length childObj3s))
Nothing -> OUndefined
varlookupForCode =
(insert "child" child) $
(insert "children" children) $
(insert "childBox" childBox) $
(insert newModuleName newModule) $
envVarlookup
(_, resultObj2s, resultObj3s)
<- runComputations
(return (argVarlookupModifier varlookupForCode,[],[]))
codeStatements
return (
contextVarLookup,
contextObj2s ++ resultObj2s,
contextObj3s ++ resultObj3s
)
return (insert newModuleName (newModule) envVarlookup, envObj2s, envObj3s)