hs-logo 0.4 → 0.5
raw patch · 23 files changed
+1016/−666 lines, 23 filesdep +HUnitdep +QuickCheckdep +colourdep −diagrams-contribdep ~diagrams-svg
Dependencies added: HUnit, QuickCheck, colour, test-framework, test-framework-hunit, test-framework-quickcheck2
Dependencies removed: diagrams-contrib
Dependency ranges changed: diagrams-svg
Files
- Logo.hs +0/−64
- Logo/Builtins.hs +0/−18
- Logo/Builtins/Arithmetic.hs +0/−34
- Logo/Builtins/Control.hs +0/−98
- Logo/Builtins/IO.hs +0/−31
- Logo/Builtins/Turtle.hs +0/−81
- Logo/Evaluator.hs +0/−158
- Logo/TokenParser.hs +0/−125
- Logo/Types.hs +0/−45
- README.md +2/−0
- hs-logo.cabal +41/−12
- src/Diagrams/TwoD/Path/Turtle.hs +112/−0
- src/Logo.hs +64/−0
- src/Logo/Builtins.hs +18/−0
- src/Logo/Builtins/Arithmetic.hs +34/−0
- src/Logo/Builtins/Control.hs +98/−0
- src/Logo/Builtins/IO.hs +31/−0
- src/Logo/Builtins/Turtle.hs +117/−0
- src/Logo/Evaluator.hs +167/−0
- src/Logo/TokenParser.hs +125/−0
- src/Logo/Types.hs +45/−0
- tests/Diagrams/TwoD/Path/Turtle/Tests.hs +151/−0
- tests/TestSuite.hs +11/−0
− Logo.hs
@@ -1,64 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-module Main where--import Diagrams.Prelude-import Diagrams.Backend.SVG.CmdLine-import Diagrams.TwoD.Path.Turtle--import Logo.Types-import Logo.TokenParser-import Logo.Builtins-import Logo.Evaluator--import System.Environment (getProgName, withArgs)--import System.Console.CmdArgs.Implicit--import qualified Data.Map as M---data LogoOpts = LogoOpts- { output :: String -- output file to write to- , src :: Maybe String -- source file to read from- } deriving (Show, Data, Typeable)--logoOpts :: String -> LogoOpts-logoOpts prog = LogoOpts- { output = "logo.svg"- &= typFile- &= help "Output image file (default=logo.svg)"- , src = def- &= typFile- &= args- }- &= summary "hs-logo Logo Interpreter v0.1"- &= program prog--main :: IO ()-main = do- prog <- getProgName- opts <- cmdArgs (logoOpts prog)- case src opts of- Nothing -> error "Source file not specified"- Just s -> renderLogo s (output opts)--renderLogo :: String -> String -> IO ()-renderLogo s o = do- tokens <- readSource s- diag <- stroke <$> runTurtleT (evaluateSourceTokens tokens)- withArgs ["-o", o, "-w", "400", "-h", "400"] $ defaultMain (diag # lw (0.005 * width diag) # centerXY # pad 1.1)--readSource :: FilePath -> IO [LogoToken]-readSource f = do- tokens <- tokenize f <$> readFile f- case tokens of- Left x -> error $ show x- Right t -> return t--evaluateSourceTokens :: [LogoToken] -> TurtleIO ()-evaluateSourceTokens tokens = do- let initialContext = LogoContext builtins M.empty M.empty- res <- evaluateWithContext tokens initialContext- case res of- Left err -> error $ show err- Right _ -> return ()
− Logo/Builtins.hs
@@ -1,18 +0,0 @@-module Logo.Builtins (builtins) where--import qualified Data.Map as M--import Logo.Types--import Logo.Builtins.Control (controlBuiltins)-import Logo.Builtins.Arithmetic (arithmeticBuiltins)-import Logo.Builtins.Turtle (turtleBuiltins)-import Logo.Builtins.IO (ioBuiltins)--builtins :: M.Map String LogoFunctionDef-builtins = foldl1 M.union $- [ controlBuiltins- , arithmeticBuiltins- , turtleBuiltins- , ioBuiltins- ]
− Logo/Builtins/Arithmetic.hs
@@ -1,34 +0,0 @@-module Logo.Builtins.Arithmetic (arithmeticBuiltins) where--import qualified Data.Map as M--import Logo.Types--sin_, cos_, tan_, arctan, sqrt_ :: [LogoToken] -> LogoEvaluator LogoToken--arithmeticBuiltins :: M.Map String LogoFunctionDef-arithmeticBuiltins = M.fromList- [ ("sin", LogoFunctionDef 1 sin_)- , ("cos", LogoFunctionDef 1 cos_)- , ("tan", LogoFunctionDef 1 tan_)- , ("arctan", LogoFunctionDef 1 arctan)- , ("sqrt", LogoFunctionDef 1 sqrt_)- ]--sin_ [NumLiteral n] = return $ NumLiteral (sin $ fromDegrees n)-sin_ _ = error "Invalid arguments for sin"--cos_ [NumLiteral n] = return $ NumLiteral (cos $ fromDegrees n)-cos_ _ = error "Invalid arguments for cos"--tan_ [NumLiteral n] = return $ NumLiteral (tan $ fromDegrees n)-tan_ _ = error "Invalid arguments for cos"--arctan [NumLiteral n] = return $ NumLiteral (atan $ fromDegrees n)-arctan _ = error "Invalid arguments for cos"--fromDegrees :: Double -> Double-fromDegrees n = n * (pi/180)--sqrt_ [NumLiteral n] = return . NumLiteral . sqrt $ n-sqrt_ _ = error "Invalid arguments to sqrt"
− Logo/Builtins/Control.hs
@@ -1,98 +0,0 @@-module Logo.Builtins.Control (controlBuiltins) where--import qualified Data.Map as M--import Control.Applicative ((<$>))--import Text.Parsec.Prim (modifyState, many)-import Text.Parsec.Combinator (manyTill)--import Logo.Types-import Logo.Evaluator--repeat_, repcount, for, dotimes, to, if_, ifelse :: [LogoToken] -> LogoEvaluator LogoToken--controlBuiltins :: M.Map String LogoFunctionDef-controlBuiltins = M.fromList- [ ("repeat", LogoFunctionDef 2 repeat_)- , ("repcount", LogoFunctionDef 0 repcount)- , ("for", LogoFunctionDef 2 for)- , ("dotimes", LogoFunctionDef 2 dotimes)- , ("to", LogoFunctionDef 0 to)- , ("if", LogoFunctionDef 2 if_)- , ("ifelse", LogoFunctionDef 3 ifelse)- ]--repeat_ (NumLiteral n : (t@(LogoList _) : [])) =- repeatWithIterCount 1- where- repeatWithIterCount x- | x > n = return $ StrLiteral ""- | otherwise = evaluateInLocalContext (M.fromList [("repcount", NumLiteral x)]) $ do- evaluateList t- repeatWithIterCount (x + 1)--repeat_ _ = error "Invalid arguments for repeat"--repcount [] = do- rc <- M.lookup "repcount" <$> getLocals- case rc of- Just c -> return c- Nothing -> error "repcount does not exist"--repcount _ = error "Invalid call to repcount"--for [ control@(LogoList _), instructionList@(LogoList _) ] = do- mapM_ loop forList- return $ StrLiteral ""- where LogoList [Identifier v, NumLiteral start, NumLiteral end, NumLiteral step] = control- forList = takeWhile withinBounds $ iterate (+ step) start- withinBounds x = if step < 0 then x >= end else x <= end- loop cur = evaluateInLocalContext (M.fromList [(v, NumLiteral cur)]) $- evaluateList instructionList--for _ = error "Invalid arguments for function 'for'"--dotimes [ control@(LogoList _), instructionList@(LogoList _) ] = do- mapM_ loop forList- return $ StrLiteral ""- where LogoList [Identifier v, NumLiteral times] = control- forList = takeWhile (< times) $ iterate (+ 1) 0- loop cur = evaluateInLocalContext (M.fromList [(v, NumLiteral cur)]) $- evaluateList instructionList--dotimes _ = error "Invalid arguments for dotimes"--if_ [StrLiteral val, ifList]- | val == "TRUE" = evaluateList ifList- | val == "FALSE" = return $ StrLiteral ""--if_ _ = undefined--ifelse [StrLiteral val, ifList, elseList]- | val == "TRUE" = evaluateList ifList- | val == "FALSE" = evaluateList elseList--ifelse _ = error "Invalid arguments for if"--to [] = do- (Identifier name) <- anyLogoToken- args <- map fromVar <$> many (satisfy isVarLiteral)- tokens <- manyTill anyLogoToken (logoToken $ Identifier "end")- modifyState (addFunction name $ LogoFunctionDef (length args) (createLogoFunction args tokens))- return $ StrLiteral ""- where- isVarLiteral (VarLiteral _) = True- isVarLiteral _ = False-- fromVar (VarLiteral s) = s- fromVar _ = undefined-- addFunction name fn ctx = ctx { functions = M.insert name fn (functions ctx) }--to _ = undefined--createLogoFunction :: [String] -> [LogoToken] -> LogoFunction-createLogoFunction vars_ tokens_ args =- evaluateInLocalContext (M.fromList $ zip vars_ args) $- evaluateTokens tokens_
− Logo/Builtins/IO.hs
@@ -1,31 +0,0 @@-module Logo.Builtins.IO (ioBuiltins) where--import qualified Data.Map as M--import Control.Applicative ((<$>))-import Control.Monad.Trans (lift, liftIO)--import System.Random (randomIO)--import Logo.Types--turtleIO :: IO a -> LogoEvaluator a-turtleIO = lift . liftIO--pr, random :: [LogoToken] -> LogoEvaluator LogoToken--ioBuiltins :: M.Map String LogoFunctionDef-ioBuiltins = M.fromList- [ ("pr", LogoFunctionDef 1 pr)- , ("random", LogoFunctionDef 1 random)- ]--pr [t] = turtleIO $ do- putStrLn (show t)- return $ StrLiteral ""--pr _ = error "Invalid arguments to pr"--random [NumLiteral n] = turtleIO $ (NumLiteral . fromIntegral . (round :: Double -> Integer) . (* n) <$> randomIO)--random _ = error "Invalid arguments to random"
− Logo/Builtins/Turtle.hs
@@ -1,81 +0,0 @@-module Logo.Builtins.Turtle (turtleBuiltins) where--import qualified Data.Map as M--import Control.Monad.Trans (lift)-import Diagrams.TwoD.Path.Turtle-import Diagrams.TwoD.Types (p2)--import Logo.Types--updateTurtle :: TurtleIO a -> LogoEvaluator a-updateTurtle = lift--fd, bk, rt, lt, home, setxy, seth, pu, pd :: [LogoToken] -> LogoEvaluator LogoToken--turtleBuiltins :: M.Map String LogoFunctionDef-turtleBuiltins = M.fromList- [ ("fd", LogoFunctionDef 1 fd)- , ("bk", LogoFunctionDef 1 bk)- , ("rt", LogoFunctionDef 1 rt)- , ("lt", LogoFunctionDef 1 lt)- , ("home", LogoFunctionDef 0 home)- , ("setxy", LogoFunctionDef 2 setxy)- , ("seth", LogoFunctionDef 1 seth)- , ("pu", LogoFunctionDef 0 pu)- , ("pd", LogoFunctionDef 0 pd)- ]--fd (NumLiteral d : []) = do- updateTurtle (forward d)- return $ StrLiteral ""--fd args = error $ "Invalid arguments to fd" ++ show args--bk (NumLiteral d : []) = do- updateTurtle (backward d)- return $ StrLiteral ""--bk _ = error "Invalid arguments to fd"--rt (NumLiteral a : []) = do- updateTurtle (right a)- return $ StrLiteral ""--rt _ = error "Invalid arguments to rt"--lt (NumLiteral a : []) = do- updateTurtle (left a)- return $ StrLiteral ""--lt _ = error "Invalid arguments to lt"--home [] = do- updateTurtle (setPos (p2 (0,0)))- return $ StrLiteral ""--home _ = error "Invalid arguments to home"--setxy [NumLiteral x, NumLiteral y] = do- updateTurtle (setPos (p2 (x,y)))- return $ StrLiteral ""--setxy _ = error "Invalid arguments to setxy"--seth [NumLiteral n] = do- updateTurtle (setHeading n)- return $ StrLiteral ""--seth _ = error "Invalid arguments to seth"--pu [] = do- updateTurtle penUp- return $ StrLiteral ""--pu _ = error "Invalid arguments to pu"--pd [] = do- updateTurtle penDown- return $ StrLiteral ""--pd _ = error "Invalid arguments to pd"
− Logo/Evaluator.hs
@@ -1,158 +0,0 @@-module Logo.Evaluator where--import Logo.Types--import qualified Data.Map as M--import Control.Monad (replicateM)-import Control.Applicative ((<$>), (<|>))-import Control.Arrow ((&&&), (***))-import Control.Monad.Trans (lift)--import Text.Parsec.Prim (runParserT, tokenPrim, getState, putState, modifyState)-import Text.Parsec.Combinator (many1, choice, chainl1)-import Text.Parsec.Error (ParseError)---- -------------------------------------------------------------------------- Expression Evaluation---- -------------------------------------------------------------------------- Expression := RelationalExpression--- RelationalExpression := AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '>=' | '<>' ) AdditiveExpression ... ]--- AdditiveExpression := MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]--- MultiplicativeExpression := PowerExpression [ ( '*' | '/' | '%' ) PowerExpression ... ]--- PowerExpression := UnaryExpression [ '^' UnaryExpression ]--- UnaryExpression := ( '-' ) UnaryExpression--- | FinalExpression--- FinalExpression := string-literal--- | number-literal--- | list--- | variable-reference--- | procedure-call--- | '(' Expression ')'--evaluateWithContext :: [LogoToken] -> LogoContext -> TurtleIO (Either ParseError ([LogoToken], LogoContext))-evaluateWithContext tokens ctx = runParserT expression ctx "(stream)" tokens--evaluateTokens :: [LogoToken] -> LogoEvaluator LogoToken-evaluateTokens [] = return $ StrLiteral ""-evaluateTokens tokens = do- ctx <- getState-- (t,s) <- lift $ do- res <- evaluateWithContext tokens ctx- case res of- Left e -> error $ show e- Right r -> return r- putState s- return $ LogoList t--evaluateList :: LogoToken -> LogoEvaluator LogoToken-evaluateList (LogoList l) = evaluateTokens l-evaluateList _ = undefined--satisfy :: (LogoToken -> Bool) -> LogoEvaluator LogoToken-satisfy f =- tokenPrim (\c -> show [c])- (\pos _ _ -> pos)- (\c -> if f c then Just c else Nothing)--logoToken :: LogoToken -> LogoEvaluator LogoToken-logoToken x = satisfy (==x)--anyLogoToken :: LogoEvaluator LogoToken-anyLogoToken = satisfy (const True)--expression :: LogoEvaluator ([LogoToken], LogoContext)-expression = do- t <- many1 relationalExpression- s <- getState- return (t,s)--relationalExpression :: LogoEvaluator LogoToken-relationalExpression = parseWithOperators ["<", ">", "=", "<=", ">=", "<>"] additiveExpression--additiveExpression :: LogoEvaluator LogoToken-additiveExpression = parseWithOperators ["+", "-"] multiplicativeExpression--multiplicativeExpression :: LogoEvaluator LogoToken-multiplicativeExpression = parseWithOperators ["*", "/", "%"] powerExpression--powerExpression :: LogoEvaluator LogoToken-powerExpression = parseWithOperators ["^"] finalExpression--finalExpression :: LogoEvaluator LogoToken-finalExpression = do- token <- anyLogoToken- case token of- Identifier s -> dispatchFn s- VarLiteral v -> lookupVar v- LogoExpr e -> do LogoList res <- evaluateTokens e- return $ head res- _ -> return token--parseWithOperators :: [String] -> LogoEvaluator LogoToken -> LogoEvaluator LogoToken-parseWithOperators operators parser = parser `chainl1` func- where- func = do op <- choice $ map (logoToken . OperLiteral) operators- return $ eval op--eval :: LogoToken -> LogoToken -> LogoToken -> LogoToken---- Arithmetic-eval (OperLiteral "+") (NumLiteral l) (NumLiteral r) = NumLiteral (l + r)-eval (OperLiteral "-") (NumLiteral l) (NumLiteral r) = NumLiteral (l - r)-eval (OperLiteral "*") (NumLiteral l) (NumLiteral r) = NumLiteral (l * r)-eval (OperLiteral "/") (NumLiteral l) (NumLiteral r) = NumLiteral (l / r)-eval (OperLiteral "%") (NumLiteral l) (NumLiteral r) = NumLiteral $ fromIntegral ((truncate l `rem` truncate r) :: Integer )-eval (OperLiteral "^") (NumLiteral l) (NumLiteral r) = NumLiteral (l ** r)---- Logical-eval (OperLiteral "<") (NumLiteral l) (NumLiteral r) = StrLiteral (if l < r then "TRUE" else "FALSE")-eval (OperLiteral ">") (NumLiteral l) (NumLiteral r) = StrLiteral (if l > r then "TRUE" else "FALSE")-eval (OperLiteral "=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l == r then "TRUE" else "FALSE")-eval (OperLiteral "<>") (NumLiteral l) (NumLiteral r) = StrLiteral (if l /= r then "TRUE" else "FALSE")-eval (OperLiteral "<=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l <= r then "TRUE" else "FALSE")-eval (OperLiteral ">=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l >= r then "TRUE" else "FALSE")---- Undefined-eval op a b = error $ "Evaluation undefined for " ++ show [op, a, b]--setLocals :: LogoSymbolTable -> LogoEvaluator ()-setLocals l = modifyState $ \s -> s { locals = l }--getLocals :: LogoEvaluator LogoSymbolTable-getLocals = locals <$> getState--evaluateInLocalContext :: LogoSymbolTable -> LogoEvaluator a -> LogoEvaluator a-evaluateInLocalContext localVars computation = do- old <- getLocals- setLocals $ localVars `M.union` old- res <- computation- setLocals old- return res--lookupVar :: String -> LogoEvaluator LogoToken-lookupVar v = do- (l,g) <- (M.lookup v *** M.lookup v) . (locals &&& globals) <$> getState- case l <|> g of- Just x -> return x- _ -> error $ "variable " ++ v ++ " not in scope"--dispatchFn :: String -> LogoEvaluator LogoToken-dispatchFn fn = do- -- get function definition- ctx <- getState- let fns = functions ctx- f = case M.lookup fn fns of- Just x -> x- _ -> error ("Function undefined: " ++ fn)- -- find arity- let (LogoFunctionDef a func) = f- -- get number of tokens- -- FIXME evaludate the token before getting a list of expressions- arguments <- replicateM a relationalExpression- -- call function and update context- func arguments
− Logo/TokenParser.hs
@@ -1,125 +0,0 @@-module Logo.TokenParser (tokenize) where--import Logo.Types--import Control.Applicative ((<|>), (<$>), many)--import Text.ParserCombinators.Parsec (- char, letter, digit, alphaNum, string, space,- parse, many1, skipMany, notFollowedBy, noneOf, try, (<?>), eof,- ParseError, Parser)--import Text.ParserCombinators.Parsec.Number (natFloat, sign)--tokenize :: String -> String -> Either ParseError [LogoToken]-tokenize progName = parse logo progName--logo :: Parser [LogoToken]-logo = do- skipMany space- expressions <- many1 logoExpr- skipMany space- eof- return $ concat expressions--logoExpr :: Parser [LogoToken]-logoExpr = try comment- <|> try list- <|> try binaryExpr- <|> try parenExpr- <|> try word- <?> "Logo Expression"--comment :: Parser [LogoToken]-comment = do- skipMany space- string ";"- skipMany $ noneOf "\n"- skipMany space- return []--word :: Parser [LogoToken]-word = try identifier- <|> try stringLiteral- <|> try varLiteral- <|> try numLiteral- <?> "Logo terminal"--identifier :: Parser [LogoToken]-identifier = do- skipMany space- s <- letter- i <- many alphaNum- return . return $ (Identifier (s:i))---- FIXME support escaping-stringLiteral :: Parser [LogoToken]-stringLiteral = do- skipMany space- char '"'- s <- many1 $ noneOf "\t\n []()\""- return . return $ StrLiteral s--varLiteral :: Parser [LogoToken]-varLiteral = do- skipMany space- char ':'- s <- letter- v <- many alphaNum- return . return $ VarLiteral (s:v)--numLiteral :: Parser [LogoToken]-numLiteral = do- skipMany space- s <- sign- n <- natFloat- return . return . NumLiteral . s $ case n of- Left i -> fromInteger i- Right f -> f--operExpr :: Parser [LogoToken]-operExpr = try parenExpr- <|> try word--binaryExpr :: Parser [LogoToken]-binaryExpr = do- lhs <- operExpr- op <- operLiteral- rhs <- try binaryExpr <|> operExpr- return . concat $ [lhs, op, rhs]--operLiteral :: Parser [LogoToken]-operLiteral = do- s <- many space- (return . OperLiteral) <$>- ( string "+"- <|> if (length s) == 0 then (string "-") else ((string "-") >> notFollowedBy digit >> (return "-"))- <|> string "*"- <|> string "/"- <|> string "%"- <|> string "^"- <|> try (string ">=")- <|> try (string "<=")- <|> try (string "<>")- <|> string "="- <|> string "<"- <|> string ">" )--list :: Parser [LogoToken]-list = do- skipMany space- char '['- expr <- many logoExpr- skipMany space- char ']'- return . return $ LogoList (concat expr)--parenExpr :: Parser [LogoToken]-parenExpr = do- skipMany space- char '('- skipMany space- expr <- many logoExpr- skipMany space- char ')'- return . return $ LogoExpr (concat expr)
− Logo/Types.hs
@@ -1,45 +0,0 @@-module Logo.Types where--import Data.Map (Map)-import Text.Parsec.Prim (ParsecT)-import Diagrams.TwoD.Path.Turtle (TurtleT)--data LogoToken = Identifier String -- Identifier- | StrLiteral String -- String Literal, e.g @"word@- | VarLiteral String -- Variable, e.g @:size@- | NumLiteral Double -- Number- | OperLiteral String -- Operator- | LogoList [LogoToken] -- Input definition/variable reference- | LogoExpr [LogoToken] -- Expression inside parentheses- deriving (Eq)--instance Show LogoToken where- show (Identifier s) = "@" ++ s ++ "@"- show (StrLiteral s) = s- show (VarLiteral s) = ":" ++ s- show (NumLiteral s) = show s- show (OperLiteral s) = s- show (LogoList l) = show l- show (LogoExpr e) = "(" ++ show e ++ ")"--type TurtleIO = TurtleT IO--type LogoEvaluator = ParsecT [LogoToken] LogoContext TurtleIO--type LogoFunction = [LogoToken] -> LogoEvaluator LogoToken--type LogoSymbolTable = Map String LogoToken--data LogoFunctionDef = LogoFunctionDef- { arity :: Int -- Number of arguments- , runFn :: LogoFunction -- Consumes an argument- } deriving Show--data LogoContext = LogoContext- { functions :: Map String LogoFunctionDef -- Functions that can be called, mapped by the identifier- , locals :: LogoSymbolTable -- Vars in local context- , globals :: LogoSymbolTable -- Vars in global context- } deriving Show---
README.md view
@@ -1,3 +1,5 @@+[](http://travis-ci.org/deepakjois/hs-logo)+ Logo interpreter written in Haskell, specialized for turtle graphics. Still very much a WIP. Lot of the language still needs to be implemented, but it is fairly functional already.
hs-logo.cabal view
@@ -1,5 +1,5 @@ Name: hs-logo-Version: 0.4+Version: 0.5 Synopsis: Logo turtle graphics interpreter Description: Interpreter for the Logo programming language, specialised for turtle graphics.@@ -12,16 +12,12 @@ Synopsis: Logo interpreter written in Haskell Category: Parser-Cabal-Version: >=1.6+Cabal-Version: >=1.8 Data-Files: README.md -Source-Repository head- type: git- location: https://github.com/deepakjois/hs-logo- Executable hs-logo Ghc-Options: -Wall -fno-warn-unused-do-bind- Hs-Source-Dirs: .+ Hs-Source-Dirs: src Main-Is: Logo.hs Other-Modules: Logo.Types Logo.TokenParser@@ -31,14 +27,47 @@ Logo.Builtins.Turtle Logo.Builtins.Arithmetic Logo.Builtins.Control- Build-Depends: base >= 4.2 && < 4.6,+ Diagrams.TwoD.Path.Turtle+ Build-Depends: base >= 4.2 && < 4.6, containers >= 0.3 && < 0.5, mtl >= 1 && < 3.0, parsec >= 3.0 && < 3.2, cmdargs >= 0.6 && <= 0.9, random >= 1.0, parsec-numbers,- diagrams-core >= 0.5 && < 0.6,- diagrams-lib >= 0.5 && < 0.6,- diagrams-contrib >= 0.1 && < 0.2,- diagrams-svg >= 0.3 && < 0.4+ colour,+ diagrams-core >= 0.5 && < 0.6,+ diagrams-lib >= 0.5 && < 0.6,+ diagrams-svg >= 0.3.3 && < 0.4++Test-suite turtle-tests+ Type: exitcode-stdio-1.0+ Hs-source-dirs: src tests+ Main-is: TestSuite.hs+ Ghc-options: -Wall++ Other-modules: Diagrams.TwoD.Path.Turtle.Tests++ Build-depends:+ HUnit >= 1.2 && < 1.3,+ QuickCheck >= 2.4 && < 2.5,+ containers >= 0.3 && < 0.5,+ test-framework >= 0.4 && < 0.7,+ test-framework-hunit >= 0.2 && < 0.3,+ test-framework-quickcheck2 >= 0.2 && < 0.3,+ -- Copied from regular dependencies+ base >= 4.2 && < 4.6,+ containers >= 0.3 && < 0.5,+ mtl >= 1 && < 3.0,+ parsec >= 3.0 && < 3.2,+ cmdargs >= 0.6 && <= 0.9,+ random >= 1.0,+ parsec-numbers,+ colour,+ diagrams-core >= 0.5 && < 0.6,+ diagrams-lib >= 0.5 && < 0.6,+ diagrams-svg >= 0.3.3 && < 0.4++Source-Repository head+ type: git+ location: https://github.com/deepakjois/hs-logo
+ src/Diagrams/TwoD/Path/Turtle.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE FlexibleContexts #-}+-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.TwoD.Path.Turtle+-- Copyright : (c) 2011 Michael Sloan+-- License : BSD-style (see LICENSE)+-- Maintainer : Michael Sloan <mgsloan at gmail>+--+-- Stateful domain specific language for diagram paths, modelled after the+-- classic \"turtle\" graphics language.+--+-----------------------------------------------------------------------------+module Diagrams.TwoD.Path.Turtle+ ( Turtle, TurtleT++ -- * Turtle control commands+ , runTurtle, runTurtleT++ -- * Motion commands+ , forward, backward, left, right++ -- * State accessors / setters+ , heading, setHeading, towards+ , pos, setPos, setPenWidth, setPenColor++ -- * Drawing control+ , penUp, penDown, isDown+ ) where++import qualified Control.Monad.State as ST+import Control.Monad.Identity (Identity(..))++import Data.Colour(Colour)+import Diagrams.Prelude+import qualified Diagrams.TwoD.Path.Turtle.Internal as T+++type TurtleT = ST.StateT T.Turtle++type Turtle = TurtleT Identity++-- | A more general way to run the turtle. Returns a computation in the+-- underlying monad @m@ yielding a path consisting of the traced trails+runTurtleT :: (Monad m, Functor m, (Renderable (Path R2) b)) => TurtleT m a -> m (Diagram b R2)+runTurtleT t = T.getTurtleDiagram . snd+ <$> ST.runStateT t T.startTurtle++-- | Run the turtle, yielding a path consisting of the traced trails.+runTurtle :: (Renderable (Path R2) b) => Turtle a -> Diagram b R2+runTurtle = runIdentity . runTurtleT++-- Motion commands++-- | Move the turtle forward, along the current heading.+forward :: Monad m => Double -> TurtleT m ()+forward x = ST.modify $ T.forward x++-- | Move the turtle backward, directly away from the current heading.+backward :: Monad m => Double -> TurtleT m ()+backward x = ST.modify $ T.backward x++-- | Modify the current heading to the left by the specified angle in degrees.+left :: Monad m => Double -> TurtleT m ()+left d = ST.modify $ T.left d++-- | Modify the current heading to the right by the specified angle in degrees.+right :: Monad m => Double -> TurtleT m ()+right d = ST.modify $ T.right d++-- State accessors / setters++-- | Set the current turtle angle, in degrees.+setHeading :: Monad m => Double -> TurtleT m ()+setHeading d = ST.modify $ T.setHeading d++-- | Get the current turtle angle, in degrees.+heading :: Monad m => TurtleT m Double+heading = ST.gets ((\(Deg x) -> x) . T.heading)++-- | Sets the heading towards a given location.+towards :: Monad m => P2 -> TurtleT m ()+towards pt = ST.modify $ T.towards pt++-- | Set the current turtle X/Y position.+setPos :: Monad m => P2 -> TurtleT m ()+setPos p = ST.modify $ T.setPenPos p++-- | Get the current turtle X/Y position.+pos :: Monad m => TurtleT m P2+pos = ST.gets T.penPos++-- Drawing control.++-- | Ends the current path, and enters into "penUp" mode+penUp :: Monad m => TurtleT m ()+penUp = ST.modify T.penUp++-- | Ends the current path, and enters into "penDown" mode+penDown :: Monad m => TurtleT m ()+penDown = ST.modify T.penDown++-- | Queries whether the pen is currently drawing a path or not.+isDown :: Monad m => TurtleT m Bool+isDown = ST.gets T.isPenDown++-- | Sets the pen color+setPenColor :: Monad m => Colour Double -> TurtleT m ()+setPenColor c = ST.modify $ T.setPenColor c++-- | Sets the pen size+setPenWidth :: Monad m => Double -> TurtleT m ()+setPenWidth s = ST.modify $ T.setPenWidth s
+ src/Logo.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Main where++import Diagrams.Prelude+import Diagrams.Backend.SVG.CmdLine+import Diagrams.TwoD.Path.Turtle++import Logo.Types+import Logo.TokenParser+import Logo.Builtins+import Logo.Evaluator++import System.Environment (getProgName, withArgs)++import System.Console.CmdArgs.Implicit++import qualified Data.Map as M+++data LogoOpts = LogoOpts+ { output :: String -- output file to write to+ , src :: Maybe String -- source file to read from+ } deriving (Show, Data, Typeable)++logoOpts :: String -> LogoOpts+logoOpts prog = LogoOpts+ { output = "logo.svg"+ &= typFile+ &= help "Output image file (default=logo.svg)"+ , src = def+ &= typFile+ &= args+ }+ &= summary "hs-logo Logo Interpreter v0.1"+ &= program prog++main :: IO ()+main = do+ prog <- getProgName+ opts <- cmdArgs (logoOpts prog)+ case src opts of+ Nothing -> error "Source file not specified"+ Just s -> renderLogo s (output opts)++renderLogo :: String -> String -> IO ()+renderLogo s o = do+ tokens <- readSource s+ diag <- runTurtleT (evaluateSourceTokens tokens)+ withArgs ["-o", o, "-w", "400", "-h", "400"] $ defaultMain (diag # lw (0.005 * width diag) # centerXY # pad 1.1)++readSource :: FilePath -> IO [LogoToken]+readSource f = do+ tokens <- tokenize f <$> readFile f+ case tokens of+ Left x -> error $ show x+ Right t -> return t++evaluateSourceTokens :: [LogoToken] -> TurtleIO ()+evaluateSourceTokens tokens = do+ let initialContext = LogoContext builtins M.empty M.empty+ res <- evaluateWithContext tokens initialContext+ case res of+ Left err -> error $ show err+ Right _ -> return ()
+ src/Logo/Builtins.hs view
@@ -0,0 +1,18 @@+module Logo.Builtins (builtins) where++import qualified Data.Map as M++import Logo.Types++import Logo.Builtins.Control (controlBuiltins)+import Logo.Builtins.Arithmetic (arithmeticBuiltins)+import Logo.Builtins.Turtle (turtleBuiltins)+import Logo.Builtins.IO (ioBuiltins)++builtins :: M.Map String LogoFunctionDef+builtins = foldl1 M.union $+ [ controlBuiltins+ , arithmeticBuiltins+ , turtleBuiltins+ , ioBuiltins+ ]
+ src/Logo/Builtins/Arithmetic.hs view
@@ -0,0 +1,34 @@+module Logo.Builtins.Arithmetic (arithmeticBuiltins) where++import qualified Data.Map as M++import Logo.Types++sin_, cos_, tan_, arctan, sqrt_ :: [LogoToken] -> LogoEvaluator LogoToken++arithmeticBuiltins :: M.Map String LogoFunctionDef+arithmeticBuiltins = M.fromList+ [ ("sin", LogoFunctionDef 1 sin_)+ , ("cos", LogoFunctionDef 1 cos_)+ , ("tan", LogoFunctionDef 1 tan_)+ , ("arctan", LogoFunctionDef 1 arctan)+ , ("sqrt", LogoFunctionDef 1 sqrt_)+ ]++sin_ [NumLiteral n] = return $ NumLiteral (sin $ fromDegrees n)+sin_ _ = error "Invalid arguments for sin"++cos_ [NumLiteral n] = return $ NumLiteral (cos $ fromDegrees n)+cos_ _ = error "Invalid arguments for cos"++tan_ [NumLiteral n] = return $ NumLiteral (tan $ fromDegrees n)+tan_ _ = error "Invalid arguments for cos"++arctan [NumLiteral n] = return $ NumLiteral (atan $ fromDegrees n)+arctan _ = error "Invalid arguments for cos"++fromDegrees :: Double -> Double+fromDegrees n = n * (pi/180)++sqrt_ [NumLiteral n] = return . NumLiteral . sqrt $ n+sqrt_ _ = error "Invalid arguments to sqrt"
+ src/Logo/Builtins/Control.hs view
@@ -0,0 +1,98 @@+module Logo.Builtins.Control (controlBuiltins) where++import qualified Data.Map as M++import Control.Applicative ((<$>))++import Text.Parsec.Prim (modifyState, many)+import Text.Parsec.Combinator (manyTill)++import Logo.Types+import Logo.Evaluator++repeat_, repcount, for, dotimes, to, if_, ifelse :: [LogoToken] -> LogoEvaluator LogoToken++controlBuiltins :: M.Map String LogoFunctionDef+controlBuiltins = M.fromList+ [ ("repeat", LogoFunctionDef 2 repeat_)+ , ("repcount", LogoFunctionDef 0 repcount)+ , ("for", LogoFunctionDef 2 for)+ , ("dotimes", LogoFunctionDef 2 dotimes)+ , ("to", LogoFunctionDef 0 to)+ , ("if", LogoFunctionDef 2 if_)+ , ("ifelse", LogoFunctionDef 3 ifelse)+ ]++repeat_ (NumLiteral n : (t@(LogoList _) : [])) =+ repeatWithIterCount 1+ where+ repeatWithIterCount x+ | x > n = return $ StrLiteral ""+ | otherwise = evaluateInLocalContext (M.fromList [("repcount", NumLiteral x)]) $ do+ evalList t+ repeatWithIterCount (x + 1)++repeat_ _ = error "Invalid arguments for repeat"++repcount [] = do+ rc <- M.lookup "repcount" <$> getLocals+ case rc of+ Just c -> return c+ Nothing -> error "repcount does not exist"++repcount _ = error "Invalid call to repcount"++for [ control@(LogoList _), instructionList@(LogoList _) ] = do+ mapM_ loop forList+ return $ StrLiteral ""+ where LogoList [Identifier v, NumLiteral start, NumLiteral end, NumLiteral step] = control+ forList = takeWhile withinBounds $ iterate (+ step) start+ withinBounds x = if step < 0 then x >= end else x <= end+ loop cur = evaluateInLocalContext (M.fromList [(v, NumLiteral cur)]) $+ evalList instructionList++for _ = error "Invalid arguments for function 'for'"++dotimes [ control@(LogoList _), instructionList@(LogoList _) ] = do+ mapM_ loop forList+ return $ StrLiteral ""+ where LogoList [Identifier v, NumLiteral times] = control+ forList = takeWhile (< times) $ iterate (+ 1) 0+ loop cur = evaluateInLocalContext (M.fromList [(v, NumLiteral cur)]) $+ evalList instructionList++dotimes _ = error "Invalid arguments for dotimes"++if_ [StrLiteral val, ifList]+ | val == "TRUE" = evalList ifList+ | val == "FALSE" = return $ StrLiteral ""++if_ _ = undefined++ifelse [StrLiteral val, ifList, elseList]+ | val == "TRUE" = evalList ifList+ | val == "FALSE" = evalList elseList++ifelse _ = error "Invalid arguments for if"++to [] = do+ (Identifier name) <- anyLogoToken+ args <- map fromVar <$> many (satisfy isVarLiteral)+ tokens <- manyTill anyLogoToken (logoToken $ Identifier "end")+ modifyState (addFunction name $ LogoFunctionDef (length args) (createLogoFunction args tokens))+ return $ StrLiteral ""+ where+ isVarLiteral (VarLiteral _) = True+ isVarLiteral _ = False++ fromVar (VarLiteral s) = s+ fromVar _ = undefined++ addFunction name fn ctx = ctx { functions = M.insert name fn (functions ctx) }++to _ = undefined++createLogoFunction :: [String] -> [LogoToken] -> LogoFunction+createLogoFunction vars_ tokens_ args =+ evaluateInLocalContext (M.fromList $ zip vars_ args) $+ evaluateTokens tokens_
+ src/Logo/Builtins/IO.hs view
@@ -0,0 +1,31 @@+module Logo.Builtins.IO (ioBuiltins) where++import qualified Data.Map as M++import Control.Applicative ((<$>))+import Control.Monad.Trans (lift, liftIO)++import System.Random (randomIO)++import Logo.Types++turtleIO :: IO a -> LogoEvaluator a+turtleIO = lift . liftIO++pr, random :: [LogoToken] -> LogoEvaluator LogoToken++ioBuiltins :: M.Map String LogoFunctionDef+ioBuiltins = M.fromList+ [ ("pr", LogoFunctionDef 1 pr)+ , ("random", LogoFunctionDef 1 random)+ ]++pr [t] = turtleIO $ do+ putStrLn (show t)+ return $ StrLiteral ""++pr _ = error "Invalid arguments to pr"++random [NumLiteral n] = turtleIO $ (NumLiteral . fromIntegral . (round :: Double -> Integer) . (* n) <$> randomIO)++random _ = error "Invalid arguments to random"
+ src/Logo/Builtins/Turtle.hs view
@@ -0,0 +1,117 @@+module Logo.Builtins.Turtle (turtleBuiltins) where++import Prelude hiding (tan)+import qualified Data.Map as M++import Control.Monad.Trans (lift)+import Diagrams.TwoD.Path.Turtle+import Diagrams.TwoD.Types (p2)+import Data.Colour (Colour)+import Data.Colour.Names++import Logo.Types++updateTurtle :: TurtleIO a -> LogoEvaluator a+updateTurtle = lift++fd, bk, rt, lt, home, setxy, seth, pu, pd, setpensize, setpencolor :: [LogoToken] -> LogoEvaluator LogoToken++turtleBuiltins :: M.Map String LogoFunctionDef+turtleBuiltins = M.fromList+ [ ("fd", LogoFunctionDef 1 fd)+ , ("bk", LogoFunctionDef 1 bk)+ , ("rt", LogoFunctionDef 1 rt)+ , ("lt", LogoFunctionDef 1 lt)+ , ("home", LogoFunctionDef 0 home)+ , ("setxy", LogoFunctionDef 2 setxy)+ , ("seth", LogoFunctionDef 1 seth)+ , ("pu", LogoFunctionDef 0 pu)+ , ("pd", LogoFunctionDef 0 pd)+ , ("setpensize", LogoFunctionDef 1 setpensize)+ , ("setpencolor", LogoFunctionDef 1 setpencolor)+ ]++fd (NumLiteral d : []) = do+ updateTurtle (forward d)+ return $ StrLiteral ""++fd args = error $ "Invalid arguments to fd" ++ show args++bk (NumLiteral d : []) = do+ updateTurtle (backward d)+ return $ StrLiteral ""++bk _ = error "Invalid arguments to fd"++rt (NumLiteral a : []) = do+ updateTurtle (right a)+ return $ StrLiteral ""++rt _ = error "Invalid arguments to rt"++lt (NumLiteral a : []) = do+ updateTurtle (left a)+ return $ StrLiteral ""++lt _ = error "Invalid arguments to lt"++home [] = do+ updateTurtle (setPos (p2 (0,0)))+ return $ StrLiteral ""++home _ = error "Invalid arguments to home"++setxy [NumLiteral x, NumLiteral y] = do+ updateTurtle (setPos (p2 (x,y)))+ return $ StrLiteral ""++setxy _ = error "Invalid arguments to setxy"++seth [NumLiteral n] = do+ updateTurtle (setHeading n)+ return $ StrLiteral ""++seth _ = error "Invalid arguments to seth"++pu [] = do+ updateTurtle penUp+ return $ StrLiteral ""++pu _ = error "Invalid arguments to pu"++pd [] = do+ updateTurtle penDown+ return $ StrLiteral ""++pd _ = error "Invalid arguments to pd"++setpensize [NumLiteral d] = do+ updateTurtle (setPenWidth d)+ return $ StrLiteral ""++setpensize _ = error "Invalid arguments to setpensize"++setpencolor [NumLiteral d] = do+ updateTurtle (setPenColor (numToColor . round $ d))+ return $ StrLiteral ""+ where+ numToColor :: Int -> Colour Double+ numToColor 0 = black+ numToColor 1 = blue+ numToColor 2 = green+ numToColor 3 = cyan+ numToColor 4 = red+ numToColor 5 = magenta+ numToColor 6 = yellow+ numToColor 7 = white+ numToColor 8 = brown+ numToColor 9 = tan+ numToColor 10 = forestgreen+ numToColor 11 = aqua+ numToColor 12 = salmon+ numToColor 13 = purple+ numToColor 14 = orange+ numToColor 15 = grey+ numToColor _ = error "Color values greater than 15 are not supported"++setpencolor _ = error "Invalid arguments to setpencolor"
+ src/Logo/Evaluator.hs view
@@ -0,0 +1,167 @@+module Logo.Evaluator where++import Logo.Types++import qualified Data.Map as M++import Control.Monad (replicateM)+import Control.Applicative ((<$>), (<|>))+import Control.Arrow ((&&&), (***))+import Control.Monad.Trans (lift)++import Text.Parsec.Prim (runParserT, tokenPrim, getState, putState, modifyState)+import Text.Parsec.Combinator (many1, choice, chainl1)+import Text.Parsec.Error (ParseError)++-- ----------------------------------------------------------------------++-- Expression Evaluation++-- ----------------------------------------------------------------------++-- Expression := RelationalExpression+-- RelationalExpression := AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '>=' | '<>' ) AdditiveExpression ... ]+-- AdditiveExpression := MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]+-- MultiplicativeExpression := PowerExpression [ ( '*' | '/' | '%' ) PowerExpression ... ]+-- PowerExpression := UnaryExpression [ '^' UnaryExpression ]+-- UnaryExpression := ( '-' ) UnaryExpression+-- | FinalExpression+-- FinalExpression := string-literal+-- | number-literal+-- | list+-- | variable-reference+-- | procedure-call+-- | '(' Expression ')'++evaluateWithContext :: [LogoToken] -> LogoContext -> TurtleIO (Either ParseError ([LogoToken], LogoContext))+evaluateWithContext tokens ctx = runParserT expression ctx "(stream)" tokens++evaluateTokens :: [LogoToken] -> LogoEvaluator LogoToken+evaluateTokens [] = return $ StrLiteral ""+evaluateTokens tokens = do+ ctx <- getState++ (t,s) <- lift $ do+ res <- evaluateWithContext tokens ctx+ case res of+ Left e -> error $ show e+ Right r -> return r+ putState s+ return $ LogoList t++satisfy :: (LogoToken -> Bool) -> LogoEvaluator LogoToken+satisfy f =+ tokenPrim (\c -> show [c])+ (\pos _ _ -> pos)+ (\c -> if f c then Just c else Nothing)++logoToken :: LogoToken -> LogoEvaluator LogoToken+logoToken x = satisfy (==x)++anyLogoToken :: LogoEvaluator LogoToken+anyLogoToken = satisfy (const True)++expression :: LogoEvaluator ([LogoToken], LogoContext)+expression = do+ t <- many1 relationalExpression+ s <- getState+ return (t,s)++relationalExpression :: LogoEvaluator LogoToken+relationalExpression = parseWithOperators ["<", ">", "=", "<=", ">=", "<>"] additiveExpression++additiveExpression :: LogoEvaluator LogoToken+additiveExpression = parseWithOperators ["+", "-"] multiplicativeExpression++multiplicativeExpression :: LogoEvaluator LogoToken+multiplicativeExpression = parseWithOperators ["*", "/", "%"] powerExpression++powerExpression :: LogoEvaluator LogoToken+powerExpression = parseWithOperators ["^"] finalExpression++finalExpression :: LogoEvaluator LogoToken+finalExpression = anyLogoToken >>= evalFinal++evalFinal, evalList, eval :: LogoToken -> LogoEvaluator LogoToken++evalFinal (Identifier s) = dispatchFn s++evalFinal (VarLiteral v) = lookupVar v++evalFinal (LogoExpr e) = do+ LogoList res <- evaluateTokens e+ return $ head res++evalFinal token = return token++evalList (LogoList l) = evaluateTokens l+evalList _ = undefined++-- Forces evaluation of a token, even if it is a list+eval token = case token of+ LogoList _ -> evalList token+ _ -> evalFinal token++parseWithOperators :: [String] -> LogoEvaluator LogoToken -> LogoEvaluator LogoToken+parseWithOperators operators parser = parser `chainl1` func+ where+ func = do op <- choice $ map (logoToken . OperLiteral) operators+ return $ evalBinOp op++evalBinOp :: LogoToken -> LogoToken -> LogoToken -> LogoToken++-- Arithmetic+evalBinOp (OperLiteral "+") (NumLiteral l) (NumLiteral r) = NumLiteral (l + r)+evalBinOp (OperLiteral "-") (NumLiteral l) (NumLiteral r) = NumLiteral (l - r)+evalBinOp (OperLiteral "*") (NumLiteral l) (NumLiteral r) = NumLiteral (l * r)+evalBinOp (OperLiteral "/") (NumLiteral l) (NumLiteral r) = NumLiteral (l / r)+evalBinOp (OperLiteral "%") (NumLiteral l) (NumLiteral r) = NumLiteral $ fromIntegral ((truncate l `rem` truncate r) :: Integer )+evalBinOp (OperLiteral "^") (NumLiteral l) (NumLiteral r) = NumLiteral (l ** r)++-- Logical+evalBinOp (OperLiteral "<") (NumLiteral l) (NumLiteral r) = StrLiteral (if l < r then "TRUE" else "FALSE")+evalBinOp (OperLiteral ">") (NumLiteral l) (NumLiteral r) = StrLiteral (if l > r then "TRUE" else "FALSE")+evalBinOp (OperLiteral "=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l == r then "TRUE" else "FALSE")+evalBinOp (OperLiteral "<>") (NumLiteral l) (NumLiteral r) = StrLiteral (if l /= r then "TRUE" else "FALSE")+evalBinOp (OperLiteral "<=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l <= r then "TRUE" else "FALSE")+evalBinOp (OperLiteral ">=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l >= r then "TRUE" else "FALSE")++-- Undefined+evalBinOp op a b = error $ "Evaluation undefined for " ++ show [op, a, b]++setLocals :: LogoSymbolTable -> LogoEvaluator ()+setLocals l = modifyState $ \s -> s { locals = l }++getLocals :: LogoEvaluator LogoSymbolTable+getLocals = locals <$> getState++evaluateInLocalContext :: LogoSymbolTable -> LogoEvaluator a -> LogoEvaluator a+evaluateInLocalContext localVars computation = do+ old <- getLocals+ setLocals $ localVars `M.union` old+ res <- computation+ setLocals old+ return res++lookupVar :: String -> LogoEvaluator LogoToken+lookupVar v = do+ (l,g) <- (M.lookup v *** M.lookup v) . (locals &&& globals) <$> getState+ case l <|> g of+ Just x -> return x+ _ -> error $ "variable " ++ v ++ " not in scope"++dispatchFn :: String -> LogoEvaluator LogoToken+dispatchFn fn = do+ -- get function definition+ ctx <- getState+ let fns = functions ctx+ f = case M.lookup fn fns of+ Just x -> x+ _ -> error ("Function undefined: " ++ fn)+ -- find arity+ let (LogoFunctionDef a func) = f+ -- get number of tokens+ -- FIXME evaludate the token before getting a list of expressions+ arguments <- replicateM a relationalExpression+ -- call function and update context+ func arguments
+ src/Logo/TokenParser.hs view
@@ -0,0 +1,125 @@+module Logo.TokenParser (tokenize) where++import Logo.Types++import Control.Applicative ((<|>), (<$>), many)++import Text.ParserCombinators.Parsec (+ char, letter, digit, alphaNum, string, space,+ parse, many1, skipMany, notFollowedBy, noneOf, try, (<?>), eof,+ ParseError, Parser)++import Text.ParserCombinators.Parsec.Number (natFloat, sign)++tokenize :: String -> String -> Either ParseError [LogoToken]+tokenize progName = parse logo progName++logo :: Parser [LogoToken]+logo = do+ skipMany space+ expressions <- many1 logoExpr+ skipMany space+ eof+ return $ concat expressions++logoExpr :: Parser [LogoToken]+logoExpr = try comment+ <|> try list+ <|> try binaryExpr+ <|> try parenExpr+ <|> try word+ <?> "Logo Expression"++comment :: Parser [LogoToken]+comment = do+ skipMany space+ string ";"+ skipMany $ noneOf "\n"+ skipMany space+ return []++word :: Parser [LogoToken]+word = try identifier+ <|> try stringLiteral+ <|> try varLiteral+ <|> try numLiteral+ <?> "Logo terminal"++identifier :: Parser [LogoToken]+identifier = do+ skipMany space+ s <- letter+ i <- many alphaNum+ return . return $ (Identifier (s:i))++-- FIXME support escaping+stringLiteral :: Parser [LogoToken]+stringLiteral = do+ skipMany space+ char '"'+ s <- many1 $ noneOf "\t\n []()\""+ return . return $ StrLiteral s++varLiteral :: Parser [LogoToken]+varLiteral = do+ skipMany space+ char ':'+ s <- letter+ v <- many alphaNum+ return . return $ VarLiteral (s:v)++numLiteral :: Parser [LogoToken]+numLiteral = do+ skipMany space+ s <- sign+ n <- natFloat+ return . return . NumLiteral . s $ case n of+ Left i -> fromInteger i+ Right f -> f++operExpr :: Parser [LogoToken]+operExpr = try parenExpr+ <|> try word++binaryExpr :: Parser [LogoToken]+binaryExpr = do+ lhs <- operExpr+ op <- operLiteral+ rhs <- try binaryExpr <|> operExpr+ return . concat $ [lhs, op, rhs]++operLiteral :: Parser [LogoToken]+operLiteral = do+ s <- many space+ (return . OperLiteral) <$>+ ( string "+"+ <|> if (length s) == 0 then (string "-") else ((string "-") >> notFollowedBy digit >> (return "-"))+ <|> string "*"+ <|> string "/"+ <|> string "%"+ <|> string "^"+ <|> try (string ">=")+ <|> try (string "<=")+ <|> try (string "<>")+ <|> string "="+ <|> string "<"+ <|> string ">" )++list :: Parser [LogoToken]+list = do+ skipMany space+ char '['+ expr <- many logoExpr+ skipMany space+ char ']'+ return . return $ LogoList (concat expr)++parenExpr :: Parser [LogoToken]+parenExpr = do+ skipMany space+ char '('+ skipMany space+ expr <- many logoExpr+ skipMany space+ char ')'+ return . return $ LogoExpr (concat expr)
+ src/Logo/Types.hs view
@@ -0,0 +1,45 @@+module Logo.Types where++import Data.Map (Map)+import Text.Parsec.Prim (ParsecT)+import Diagrams.TwoD.Path.Turtle (TurtleT)++data LogoToken = Identifier String -- Identifier+ | StrLiteral String -- String Literal, e.g @"word@+ | VarLiteral String -- Variable, e.g @:size@+ | NumLiteral Double -- Number+ | OperLiteral String -- Operator+ | LogoList [LogoToken] -- Input definition/variable reference+ | LogoExpr [LogoToken] -- Expression inside parentheses+ deriving (Eq)++instance Show LogoToken where+ show (Identifier s) = "@" ++ s ++ "@"+ show (StrLiteral s) = s+ show (VarLiteral s) = ":" ++ s+ show (NumLiteral s) = show s+ show (OperLiteral s) = s+ show (LogoList l) = show l+ show (LogoExpr e) = "(" ++ show e ++ ")"++type TurtleIO = TurtleT IO++type LogoEvaluator = ParsecT [LogoToken] LogoContext TurtleIO++type LogoFunction = [LogoToken] -> LogoEvaluator LogoToken++type LogoSymbolTable = Map String LogoToken++data LogoFunctionDef = LogoFunctionDef+ { arity :: Int -- Number of arguments+ , runFn :: LogoFunction -- Consumes an argument+ } deriving Show++data LogoContext = LogoContext+ { functions :: Map String LogoFunctionDef -- Functions that can be called, mapped by the identifier+ , locals :: LogoSymbolTable -- Vars in local context+ , globals :: LogoSymbolTable -- Vars in global context+ } deriving Show+++
+ tests/Diagrams/TwoD/Path/Turtle/Tests.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, ViewPatterns #-}+module Diagrams.TwoD.Path.Turtle.Tests+ ( tests+ ) where++import Control.Arrow ((***))++import Test.Framework+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck++import Diagrams.Prelude+import Diagrams.TwoD.Path.Turtle.Internal++import Debug.Trace++tests =+ [ testProperty "Moves forward correctly" movesForward+ , testProperty "Moves backward correctly" movesBackward+ , testProperty "Moves backward and forward correctly" movesBackwardAndForward+ , testProperty "Moves left correctly" movesLeft+ , testProperty "Moves right correctly" movesRight+ , testProperty "Current trail is empty when pen is up" trailEmptyWhenPenUp+ ]++-- | The turtle moves forward by the right distance+movesForward :: Turtle+ -> Property+movesForward t = isPenDown t ==>+ round diffPos == round x -- position is set correctly+ && round lenCurrTrail == round x -- most recent trail has the right length+ where+ x = 2.0+ t' = t # forward x+ diffPos = magnitude $ penPos t' .-. penPos t+ lenCurrTrail = flip arcLength 0.0001 . head . trailSegments . snd . currTrail $ t'++-- | The turtle moves forward by the right distance+movesBackward :: Turtle+ -> Property+movesBackward t = isPenDown t ==>+ round diffPos == round x -- position is set correctly+ && round lenCurrTrail == round x -- most recent trail has the right length+ where+ x = 2.0+ t' = t # backward x+ diffPos = magnitude $ penPos t' .-. penPos t+ lenCurrTrail = flip arcLength 0.0001 . head . trailSegments . snd . currTrail $ t'++-- | The turtle moves forward and backward by the same distance and returns to+-- the same position+movesBackwardAndForward :: Turtle+ -> Property+movesBackwardAndForward t = isPenDown t ==>+ abs(endX - startX) < 0.0001+ && abs(endY - startY) < 0.0001+ && totalSegmentsAdded == 2+ where+ x = 2.0+ t' = t # forward x # backward x+ (unp2 -> (startX, startY)) = penPos t+ (unp2 -> (endX, endY)) = penPos t'+ totalSegmentsAdded = (uncurry (-)) . (getTrailLength *** getTrailLength) $ (t',t)+ getTrailLength = (length . trailSegments . snd . currTrail)++-- | The turtle moves left four times and returns to the same position+movesLeft :: Turtle+ -> Property+movesLeft t = isPenDown t ==>+ abs(endX - startX) < 0.0001+ && abs(endY - startY) < 0.0001+ where+ x = 2.0+ turn = 90+ t' = t # forward x # left turn+ # forward x # left turn+ # forward x # left turn+ # forward x+ (unp2 -> (startX, startY)) = penPos t+ (unp2 -> (endX, endY)) = penPos t'++-- | The turtle moves right four times and returns to the same position+movesRight :: Turtle+ -> Property+movesRight t = isPenDown t ==>+ abs(endX - startX) < 0.0001+ && abs(endY - startY) < 0.0001+ where+ x = 2.0+ turn = 90+ t' = t # forward x # right turn+ # forward x # right turn+ # forward x # right turn+ # forward x+ (unp2 -> (startX, startY)) = penPos t+ (unp2 -> (endX, endY)) = penPos t'++-- | When the trail is empty, @currTrail@ always remains empty and no new paths+-- are added+trailEmptyWhenPenUp :: Turtle+ -> Property+trailEmptyWhenPenUp t = isPenDown t ==> trailIsEmpty+ where+ t' = t # penUp # forward 4 # backward 3+ trailIsEmpty = null . trailSegments . snd . currTrail $ t'++instance Show Turtle where+ show t@(Turtle a b c _ _ _) = show (a,b,c)+++-- | Arbitrary instance for the Turtle type.+instance Arbitrary Turtle where+ arbitrary =+ Turtle <$> arbitrary+ <*> arbitrary+ <*> (Deg <$> arbitrary)+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary++-- | Arbitrary instance for Diagrams type P2+instance Arbitrary P2 where+ arbitrary = p2 <$> arbitrary++-- | Arbitrary instance for TurtlePath+instance Arbitrary TurtlePath where+ arbitrary = TurtlePath <$> arbitrary <*> arbitrary++-- | Arbitrary instance of PenStyle+--+-- The color of the pen is chosen from black, blue or brown only+instance Arbitrary PenStyle where+ arbitrary = do+ penWidth_ <- arbitrary+ colorCode <- choose (1,3) :: Gen Int+ case colorCode of+ 1 -> return $ PenStyle penWidth_ black+ 2 -> return $ PenStyle penWidth_ blue+ 3 -> return $ PenStyle penWidth_ brown++-- | Arbitrary instance of Segment+--+-- Currently this only generates linear segments only+instance Arbitrary (Segment R2) where+ arbitrary = do+ h <- Deg <$> arbitrary+ x <- r2 <$> arbitrary+ return $ rotate h (Linear x)++instance Arbitrary (Trail R2) where+ arbitrary = Trail <$> arbitrary <*> (return False)
+ tests/TestSuite.hs view
@@ -0,0 +1,11 @@+-- | Main module to run all tests.+--+module Main where++import Test.Framework (defaultMain, testGroup)++import qualified Diagrams.TwoD.Path.Turtle.Tests++main :: IO ()+main = defaultMain+ [ testGroup "Diagrams.TwoD.Path.Turtle.Tests" Diagrams.TwoD.Path.Turtle.Tests.tests ]